OfficeScenarioTests CoopShares+Assets #121
3
.aliases
3
.aliases
@ -95,3 +95,6 @@ if [ ! -f .environment ]; then
|
||||
cp .tc-environment .environment
|
||||
fi
|
||||
source .environment
|
||||
|
||||
alias scenario-reports-upload='./gradlew scenarioTests convertMarkdownToHtml && ssh hsh03-hsngdev@h50.hostsharing.net "rm -f doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office/*.html" && scp build/doc/scenarios/*.html hsh03-hsngdev@h50.hostsharing.net:doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office'
|
||||
alias scenario-reports-open='open https://hsngdev.hs-example.de/scenarios/office'
|
||||
|
@ -410,7 +410,7 @@ tasks.register('convertMarkdownToHtml') {
|
||||
group = 'Conversion'
|
||||
|
||||
// Define the template file and input directory
|
||||
def templateFile = file('doc/scenarios/template.html')
|
||||
def templateFile = file('doc/scenarios/.template.html')
|
||||
|
||||
// Task configuration and execution
|
||||
doFirst {
|
||||
@ -425,13 +425,13 @@ tasks.register('convertMarkdownToHtml') {
|
||||
|
||||
// Check if the template file exists
|
||||
if (!templateFile.exists()) {
|
||||
throw new GradleException("Template file 'doc/scenarios/template.html' not found.")
|
||||
throw new GradleException("Template file 'doc/scenarios/.template.html' not found.")
|
||||
}
|
||||
}
|
||||
|
||||
doLast {
|
||||
// Gather all Markdown files in the current directory
|
||||
fileTree(dir: '.', include: 'doc/scenarios/*.md').each { file ->
|
||||
fileTree(dir: '.', include: 'build/doc/scenarios/*.md').each { file ->
|
||||
// Corrected way to create the output file path
|
||||
def outputFile = new File(file.parent, file.name.replaceAll(/\.md$/, '.html'))
|
||||
|
||||
@ -444,3 +444,4 @@ tasks.register('convertMarkdownToHtml') {
|
||||
}
|
||||
}
|
||||
}
|
||||
convertMarkdownToHtml.dependsOn scenarioTests
|
||||
|
110
doc/business-glossary-de.md
Normal file
110
doc/business-glossary-de.md
Normal file
@ -0,0 +1,110 @@
|
||||
### hsadminNg fachliches Glossar
|
||||
|
||||
<!--
|
||||
Currently, this business glossary is only available in German because in many cases,
|
||||
the German terms are important for comprehensibility for those using this software.
|
||||
-->
|
||||
|
||||
Dieses ist eine Sammlung von Fachbegriffen, die in diesem Projekt benutzt werden.
|
||||
Ebenfalls aufgenommen sind technische Begriffe, die für Benutzer für das Verständnis der Schnittstellen nötig sind.
|
||||
|
||||
Falls etwas fehlt, bitte Bescheid geben.
|
||||
|
||||
|
||||
#### Partner
|
||||
|
||||
In diesem System ist ein _Partner_ grundsätzlich jeglicher Geschäftspartner der _Hostsharing eG_.
|
||||
Dies können grundsätzlich Kunden, siehe [Debitor](#Debitor), wie Lieferanten sein.
|
||||
Derzeit sind aber nur Debitoren implementiert.
|
||||
|
||||
Des Weiteren gibt es für jeden _Partner_ eine fünfstellige Partnernummer mit dem Prefix 'P-' (z.B. `P-123454`)
|
||||
sowie Zusatzinformationen (z.B. Registergerichtnummer oder Geburtsdatum), die zur genauen Identifikation benötigt werden.
|
||||
|
||||
Für einen _Partner_ kann es gleichzeitig mehrere [Debitoren](#Debitor)
|
||||
und zeitlich nacheinander mehrere [Mitgliedschaften](#Mitgliedschaft) geben.
|
||||
|
||||
Partner sind grundsätzlich als ist [Relation](#Relation) der Vertragsperson mit der Person _Hostsharing eG_ implementiert.
|
||||
|
||||
|
||||
### Debitor
|
||||
|
||||
Ein `Debitor` ist quasi ein Rechnungsempfänger für einen [Partner](#Partner).
|
||||
|
||||
Für einen _Partner_ kann es gleichzeitig mehrere [Debitoren](#Debitor) geben,
|
||||
z.B. für spezielle Projekte des Kunden oder verbundene Organisationen.
|
||||
|
||||
Des Weiteren gibt es für jeden _Partner_ eine fünfstellige Partnernummer mit dem Prefix 'P-' (z.B. `P-123454`)
|
||||
sowie Zusatzinformationen (z.B. Registergerichtsnummer oder Geburtsdatum), die zur genauen Identifikation benötigt werden.
|
||||
|
||||
Debitoren sind grundsätzlich als ist [Relation](#Relation) der Vertragsperson mit der Person des Vertragspartners implementiert.
|
||||
|
||||
|
||||
#### Relation
|
||||
|
||||
Eine _Relation_ ist eine typisierte und mit Kontaktdaten versehene Beziehung einer (_Holder_)-Person zu einer _Anchor_-Person.
|
||||
|
||||
Eine Relation ist eine Art Geschäftsrolle, wir haben hier aber keinen Begriff mit 'Rolle' verwendet,
|
||||
weil 'Role' (engl.) zu leicht mit der [RBAC-Rolle](#RBAC-Role) verwechselt werden könnte.
|
||||
|
||||
Die _Relation_ ist auch ein technisches Konzept und gehört nicht zur Domänensprache.
|
||||
Dieses Konzept ist jedoch für das Verständnis der ([API](#API)) notwendig.
|
||||
|
||||
|
||||
#### Ex-Partner
|
||||
|
||||
Ex-Partner bilden [Personen](#Person) ab, die vormals [Partner](#Partner) waren.
|
||||
Diese bleiben dadurch informationshalber im System verfügbar.
|
||||
|
||||
Implementiert ist der _Ex-Partner_ als eine besondere Form der [Relation](#Relation)
|
||||
der Person des Ex-Partner (_Holder_) zum neuen Partner (_Anchor_) dargestellt.
|
||||
Dieses kann zu einer Kettenbildung führen.
|
||||
|
||||
|
||||
#### Representative-Contact (ehemals _contractual_)
|
||||
|
||||
Ein _Representative_ ist eine natürliche Person, die für eine nicht-natürliche Person vertretungsberechtigt ist.
|
||||
|
||||
Implementiert ist der _Representative_ als eine besondere Form der [Relation](#Relation)
|
||||
der Person des Repräsentanten (_Holder_) zur repräsentierten Person (_Anchor_) dargestellt.
|
||||
|
||||
|
||||
### VIP-Contact
|
||||
|
||||
Ein _VIP-Contact_ ist eine natürliche Person, die für einen Geschäftspartner eine wichtige Funktion übernimmt,
|
||||
nicht aber deren offizieller Repräsentant ist.
|
||||
|
||||
Implementiert ist der _VIP-Contact_ als eine besondere Form der [Relation](#Relation)
|
||||
der Person des VIP-Contact (_Holder_) zur repräsentierten Person (_Anchor_) dargestellt.
|
||||
|
||||
|
||||
### Operations-Contact
|
||||
|
||||
Ein _Operations-_Contact_ ist_ eine natürliche Person, die für einen Geschäftspartner technischer Ansprechpartner ist
|
||||
|
||||
Implementiert ist der _Operations-Contact_ als eine besondere Form der [Relation](#Relation)
|
||||
der Person des _Operations-Contact_ (_Holder_) zur repräsentierten Person (_Anchor_) dargestellt.
|
||||
|
||||
|
||||
### Subscriber-Contact
|
||||
|
||||
Ein _Subscriber-_Contact_ ist_ eine natürliche Person, die für einen Geschäftspartner eine bestimmte Mailingliste abonniert.
|
||||
|
||||
Implementiert ist der _Subscriber-Contact_ als eine besondere Form der [Relation](#Relation)
|
||||
der Person des _Subscriber-Contact_ (_Holder_) zur repräsentierten Person (_Anchor_) dargestellt.
|
||||
Zusätzlich wird diese Relation mit dem Kurznamen der abonnierten Mailingliste markiert.
|
||||
|
||||
|
||||
#### Anchor / Relation-Anchor
|
||||
|
||||
siehe [Relation](#Relation)
|
||||
|
||||
|
||||
#### Holder / Relation-Holder
|
||||
|
||||
siehe [Relation](#Relation)
|
||||
|
||||
|
||||
#### API
|
||||
|
||||
Und API (Application-Programming-Interface) verstehen wir eine über HTTPS angesprochene programmatisch bedienbare Schnittstell
|
||||
zur Funktionalität des hsAdmin-NG-Systems.
|
1
doc/scenarios/README.txt
Normal file
1
doc/scenarios/README.txt
Normal file
@ -0,0 +1 @@
|
||||
find the generated ScenarioReports in build/doc/scenarios
|
@ -1,6 +1,7 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.openapitools.jackson.nullable.JsonNullableModule;
|
||||
@ -9,15 +10,20 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
|
||||
@Configuration
|
||||
public class JsonObjectMapperConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public Jackson2ObjectMapperBuilder customObjectMapper() {
|
||||
// HOWTO: add JSON converters and specify other JSON mapping configurations
|
||||
return new Jackson2ObjectMapperBuilder()
|
||||
.modules(new JsonNullableModule(), new JavaTimeModule())
|
||||
.featuresToEnable(JsonParser.Feature.ALLOW_COMMENTS, JsonParser.Feature.ALLOW_COMMENTS)
|
||||
.featuresToEnable(
|
||||
JsonParser.Feature.ALLOW_COMMENTS,
|
||||
DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS
|
||||
)
|
||||
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ public class CustomErrorResponse {
|
||||
this.path = path;
|
||||
this.statusCode = status.value();
|
||||
this.statusPhrase = status.getReasonPhrase();
|
||||
// HOWTO: debug serverside error response - set a breakpoint here
|
||||
this.message = message.startsWith("ERROR: [") ? message : "ERROR: [" + statusCode + "] " + message;
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<List<HsOfficeCoopAssetsTransactionResource>> listCoopAssets(
|
||||
public ResponseEntity<List<HsOfficeCoopAssetsTransactionResource>> getListOfCoopAssets(
|
||||
final String currentSubject,
|
||||
final String assumedRoles,
|
||||
final UUID membershipUuid,
|
||||
@ -55,7 +55,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<HsOfficeCoopAssetsTransactionResource> addCoopAssetsTransaction(
|
||||
public ResponseEntity<HsOfficeCoopAssetsTransactionResource> postNewCoopAssetTransaction(
|
||||
final String currentSubject,
|
||||
final String assumedRoles,
|
||||
final HsOfficeCoopAssetsTransactionInsertResource requestBody) {
|
||||
@ -77,7 +77,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<HsOfficeCoopAssetsTransactionResource> getCoopAssetTransactionByUuid(
|
||||
public ResponseEntity<HsOfficeCoopAssetsTransactionResource> getSingleCoopAssetTransactionByUuid(
|
||||
final String currentSubject, final String assumedRoles, final UUID assetTransactionUuid) {
|
||||
|
||||
context.define(currentSubject, assumedRoles);
|
||||
@ -128,9 +128,9 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
}
|
||||
|
||||
final BiConsumer<HsOfficeCoopAssetsTransactionInsertResource, HsOfficeCoopAssetsTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
if ( resource.getReverseEntryUuid() != null ) {
|
||||
entity.setAdjustedAssetTx(coopAssetsTransactionRepo.findByUuid(resource.getReverseEntryUuid())
|
||||
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] reverseEntityUuid %s not found".formatted(resource.getReverseEntryUuid()))));
|
||||
if ( resource.getRevertedAssetTxUuid() != null ) {
|
||||
entity.setRevertedAssetTx(coopAssetsTransactionRepo.findByUuid(resource.getRevertedAssetTxUuid())
|
||||
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] reverseEntityUuid %s not found".formatted(resource.getRevertedAssetTxUuid()))));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -50,8 +50,8 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
||||
.withProp(HsOfficeCoopAssetsTransactionEntity::getAssetValue)
|
||||
.withProp(HsOfficeCoopAssetsTransactionEntity::getReference)
|
||||
.withProp(HsOfficeCoopAssetsTransactionEntity::getComment)
|
||||
.withProp(at -> ofNullable(at.getAdjustedAssetTx()).map(HsOfficeCoopAssetsTransactionEntity::toShortString).orElse(null))
|
||||
.withProp(at -> ofNullable(at.getAdjustmentAssetTx()).map(HsOfficeCoopAssetsTransactionEntity::toShortString).orElse(null))
|
||||
.withProp(at -> ofNullable(at.getRevertedAssetTx()).map(HsOfficeCoopAssetsTransactionEntity::toShortString).orElse(null))
|
||||
.withProp(at -> ofNullable(at.getReversalAssetTx()).map(HsOfficeCoopAssetsTransactionEntity::toShortString).orElse(null))
|
||||
.quotedValues(false);
|
||||
|
||||
@Id
|
||||
@ -77,7 +77,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
||||
* The signed value which directly affects the booking balance.
|
||||
*
|
||||
* <p>This means, that a DEPOSIT is always positive, a DISBURSAL is always negative,
|
||||
* but an ADJUSTMENT can bei either positive or negative.
|
||||
* but an REVERSAL can bei either positive or negative.
|
||||
* See {@link HsOfficeCoopAssetsTransactionType} for</p> more information.
|
||||
*/
|
||||
@Column(name = "assetvalue")
|
||||
@ -96,14 +96,14 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
||||
private String comment;
|
||||
|
||||
/**
|
||||
* Optionally, the UUID of the corresponding transaction for an adjustment transaction.
|
||||
* Optionally, the UUID of the corresponding transaction for an reversal transaction.
|
||||
*/
|
||||
@OneToOne
|
||||
@JoinColumn(name = "adjustedassettxuuid")
|
||||
private HsOfficeCoopAssetsTransactionEntity adjustedAssetTx;
|
||||
@JoinColumn(name = "revertedassettxuuid")
|
||||
private HsOfficeCoopAssetsTransactionEntity revertedAssetTx;
|
||||
|
||||
@OneToOne(mappedBy = "adjustedAssetTx")
|
||||
private HsOfficeCoopAssetsTransactionEntity adjustmentAssetTx;
|
||||
@OneToOne(mappedBy = "revertedAssetTx")
|
||||
private HsOfficeCoopAssetsTransactionEntity reversalAssetTx;
|
||||
|
||||
@Override
|
||||
public HsOfficeCoopAssetsTransactionEntity load() {
|
||||
|
@ -4,7 +4,7 @@ public enum HsOfficeCoopAssetsTransactionType {
|
||||
/**
|
||||
* correction of wrong bookings, value can be positive or negative
|
||||
*/
|
||||
ADJUSTMENT,
|
||||
REVERSAL,
|
||||
|
||||
/**
|
||||
* payment received from member after signing shares, value >0
|
||||
|
@ -38,7 +38,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> listCoopShares(
|
||||
public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> getListOfCoopShares(
|
||||
final String currentSubject,
|
||||
final String assumedRoles,
|
||||
final UUID membershipUuid,
|
||||
@ -57,7 +57,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<HsOfficeCoopSharesTransactionResource> addCoopSharesTransaction(
|
||||
public ResponseEntity<HsOfficeCoopSharesTransactionResource> postNewCoopSharesTransaction(
|
||||
final String currentSubject,
|
||||
final String assumedRoles,
|
||||
final HsOfficeCoopSharesTransactionInsertResource requestBody) {
|
||||
@ -80,7 +80,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<HsOfficeCoopSharesTransactionResource> getCoopShareTransactionByUuid(
|
||||
public ResponseEntity<HsOfficeCoopSharesTransactionResource> getSingleCoopShareTransactionByUuid(
|
||||
final String currentSubject, final String assumedRoles, final UUID shareTransactionUuid) {
|
||||
|
||||
context.define(currentSubject, assumedRoles);
|
||||
@ -131,9 +131,9 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
}
|
||||
|
||||
final BiConsumer<HsOfficeCoopSharesTransactionInsertResource, HsOfficeCoopSharesTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
if ( resource.getAdjustedShareTxUuid() != null ) {
|
||||
entity.setAdjustedShareTx(coopSharesTransactionRepo.findByUuid(resource.getAdjustedShareTxUuid())
|
||||
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] adjustedShareTxUuid %s not found".formatted(resource.getAdjustedShareTxUuid()))));
|
||||
if ( resource.getRevertedShareTxUuid() != null ) {
|
||||
entity.setRevertedShareTx(coopSharesTransactionRepo.findByUuid(resource.getRevertedShareTxUuid())
|
||||
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] revertedShareTxUuid %s not found".formatted(resource.getRevertedShareTxUuid()))));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -48,8 +48,8 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
|
||||
.withProp(HsOfficeCoopSharesTransactionEntity::getShareCount)
|
||||
.withProp(HsOfficeCoopSharesTransactionEntity::getReference)
|
||||
.withProp(HsOfficeCoopSharesTransactionEntity::getComment)
|
||||
.withProp(at -> ofNullable(at.getAdjustedShareTx()).map(HsOfficeCoopSharesTransactionEntity::toShortString).orElse(null))
|
||||
.withProp(at -> ofNullable(at.getAdjustmentShareTx()).map(HsOfficeCoopSharesTransactionEntity::toShortString).orElse(null))
|
||||
.withProp(at -> ofNullable(at.getRevertedShareTx()).map(HsOfficeCoopSharesTransactionEntity::toShortString).orElse(null))
|
||||
.withProp(at -> ofNullable(at.getReversalShareTx()).map(HsOfficeCoopSharesTransactionEntity::toShortString).orElse(null))
|
||||
.quotedValues(false);
|
||||
|
||||
@Id
|
||||
@ -71,7 +71,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
|
||||
* The signed value which directly affects the booking balance.
|
||||
*
|
||||
* <p>This means, that a SUBSCRIPTION is always positive, a CANCELLATION is always negative,
|
||||
* but an ADJUSTMENT can bei either positive or negative.
|
||||
* but an REVERSAL can bei either positive or negative.
|
||||
* See {@link HsOfficeCoopSharesTransactionType} for</p> more information.
|
||||
*/
|
||||
@Column(name = "valuedate")
|
||||
@ -93,14 +93,14 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
|
||||
private String comment;
|
||||
|
||||
/**
|
||||
* Optionally, the UUID of the corresponding transaction for an adjustment transaction.
|
||||
* Optionally, the UUID of the corresponding transaction for a REVERSAL transaction.
|
||||
*/
|
||||
@OneToOne
|
||||
@JoinColumn(name = "adjustedsharetxuuid")
|
||||
private HsOfficeCoopSharesTransactionEntity adjustedShareTx;
|
||||
@JoinColumn(name = "revertedsharetxuuid")
|
||||
private HsOfficeCoopSharesTransactionEntity revertedShareTx;
|
||||
|
||||
@OneToOne(mappedBy = "adjustedShareTx")
|
||||
private HsOfficeCoopSharesTransactionEntity adjustmentShareTx;
|
||||
@OneToOne(mappedBy = "revertedShareTx")
|
||||
private HsOfficeCoopSharesTransactionEntity reversalShareTx;
|
||||
|
||||
@Override
|
||||
public HsOfficeCoopSharesTransactionEntity load() {
|
||||
|
@ -2,9 +2,9 @@ package net.hostsharing.hsadminng.hs.office.coopshares;
|
||||
|
||||
public enum HsOfficeCoopSharesTransactionType {
|
||||
/**
|
||||
* correction of wrong bookings, with either positive or negative value
|
||||
* reversal of wrong bookings, with either positive or negative value identical to reversed transaction
|
||||
*/
|
||||
ADJUSTMENT,
|
||||
REVERSAL,
|
||||
|
||||
/**
|
||||
* shares signed, e.g. with the declaration of accession, value >0
|
||||
|
@ -16,6 +16,8 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
@RestController
|
||||
|
||||
public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
||||
@ -39,7 +41,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
final var entities = ( memberNumber != null)
|
||||
? List.of(membershipRepo.findMembershipByMemberNumber(memberNumber))
|
||||
? ofNullable(membershipRepo.findMembershipByMemberNumber(memberNumber)).stream().toList()
|
||||
: membershipRepo.findMembershipsByOptionalPartnerUuid(partnerUuid);
|
||||
|
||||
final var resources = mapper.mapList(entities, HsOfficeMembershipResource.class,
|
||||
|
@ -14,14 +14,16 @@ public interface HsOfficeMembershipRepository extends Repository<HsOfficeMembers
|
||||
|
||||
HsOfficeMembershipEntity save(final HsOfficeMembershipEntity entity);
|
||||
|
||||
List<HsOfficeMembershipEntity> findAll();
|
||||
|
||||
@Query("""
|
||||
SELECT membership FROM HsOfficeMembershipEntity membership
|
||||
WHERE ( CAST(:partnerUuid as org.hibernate.type.UUIDCharType) IS NULL
|
||||
OR membership.partner.uuid = :partnerUuid )
|
||||
ORDER BY membership.partner.partnerNumber, membership.memberNumberSuffix
|
||||
""")
|
||||
""")
|
||||
List<HsOfficeMembershipEntity> findMembershipsByOptionalPartnerUuid(UUID partnerUuid);
|
||||
|
||||
@Query("""
|
||||
SELECT membership FROM HsOfficeMembershipEntity membership
|
||||
WHERE (:partnerNumber = membership.partner.partnerNumber)
|
||||
@ -31,10 +33,12 @@ public interface HsOfficeMembershipRepository extends Repository<HsOfficeMembers
|
||||
HsOfficeMembershipEntity findMembershipByPartnerNumberAndSuffix(
|
||||
@NotNull Integer partnerNumber,
|
||||
@NotNull String suffix);
|
||||
|
||||
default HsOfficeMembershipEntity findMembershipByMemberNumber(Integer memberNumber) {
|
||||
final var partnerNumber = memberNumber / 100;
|
||||
final var suffix = memberNumber % 100;
|
||||
return findMembershipByPartnerNumberAndSuffix(partnerNumber, String.format("%02d", suffix));
|
||||
final String suffix = String.format("%02d", memberNumber % 100);
|
||||
final var result = findMembershipByPartnerNumberAndSuffix(partnerNumber, suffix);
|
||||
return result;
|
||||
}
|
||||
|
||||
long count();
|
||||
|
@ -143,6 +143,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
|
||||
private void optionallyCreateExPartnerRelation(final HsOfficePartnerEntity saved, final HsOfficeRelationRealEntity previousPartnerRel) {
|
||||
if (!saved.getPartnerRel().getUuid().equals(previousPartnerRel.getUuid())) {
|
||||
// TODO.impl: we also need to use the new partner-person as the anchor
|
||||
relationRepo.save(previousPartnerRel.toBuilder().uuid(null).type(EX_PARTNER).build());
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
// HOWTO: convert data types for exchange between PostgreSQL and Java/Hibernate/JPA-Entities
|
||||
@Converter(autoApply = true)
|
||||
public class HsOfficePersonTypeConverter implements AttributeConverter<HsOfficePersonType, String> {
|
||||
|
||||
|
@ -14,6 +14,7 @@ public class SystemProcess {
|
||||
|
||||
@Getter
|
||||
private String stdOut;
|
||||
|
||||
@Getter
|
||||
private String stdErr;
|
||||
|
||||
@ -21,7 +22,6 @@ public class SystemProcess {
|
||||
this.processBuilder = new ProcessBuilder(command);
|
||||
}
|
||||
|
||||
|
||||
public String getCommand() {
|
||||
return processBuilder.command().toString();
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ components:
|
||||
HsOfficeCoopAssetsTransactionType:
|
||||
type: string
|
||||
enum:
|
||||
- ADJUSTMENT
|
||||
- REVERSAL
|
||||
- DEPOSIT
|
||||
- DISBURSAL
|
||||
- TRANSFER
|
||||
@ -32,15 +32,15 @@ components:
|
||||
type: string
|
||||
comment:
|
||||
type: string
|
||||
adjustedAssetTx:
|
||||
revertedAssetTx:
|
||||
$ref: '#/components/schemas/HsOfficeReferencedCoopAssetsTransaction'
|
||||
adjustmentAssetTx:
|
||||
reversalAssetTx:
|
||||
$ref: '#/components/schemas/HsOfficeReferencedCoopAssetsTransaction'
|
||||
|
||||
HsOfficeReferencedCoopAssetsTransaction:
|
||||
description:
|
||||
Similar to `HsOfficeCoopAssetsTransaction` but without the self-referencing properties
|
||||
(`adjustedAssetTx` and `adjustmentAssetTx`), to avoid recursive JSON.
|
||||
(`revertedAssetTx` and `reversalAssetTx`), to avoid recursive JSON.
|
||||
type: object
|
||||
properties:
|
||||
uuid:
|
||||
@ -80,7 +80,7 @@ components:
|
||||
maxLength: 48
|
||||
comment:
|
||||
type: string
|
||||
reverseEntry.uuid:
|
||||
revertedAssetTx.uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
required:
|
||||
|
@ -2,7 +2,7 @@ get:
|
||||
tags:
|
||||
- hs-office-coopAssets
|
||||
description: 'Fetch a single asset transaction by its uuid, if visible for the current subject.'
|
||||
operationId: getCoopAssetTransactionByUuid
|
||||
operationId: getSingleCoopAssetTransactionByUuid
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentSubject'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
|
@ -3,7 +3,7 @@ get:
|
||||
description: Returns the list of (optionally filtered) cooperative asset transactions which are visible to the current subject or any of it's assumed roles.
|
||||
tags:
|
||||
- hs-office-coopAssets
|
||||
operationId: listCoopAssets
|
||||
operationId: getListOfCoopAssets
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentSubject'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
@ -46,7 +46,7 @@ post:
|
||||
summary: Adds a new cooperative asset transaction.
|
||||
tags:
|
||||
- hs-office-coopAssets
|
||||
operationId: addCoopAssetsTransaction
|
||||
operationId: postNewCoopAssetTransaction
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentSubject'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
|
@ -6,7 +6,7 @@ components:
|
||||
HsOfficeCoopSharesTransactionType:
|
||||
type: string
|
||||
enum:
|
||||
- ADJUSTMENT
|
||||
- REVERSAL
|
||||
- SUBSCRIPTION
|
||||
- CANCELLATION
|
||||
|
||||
@ -27,15 +27,15 @@ components:
|
||||
type: string
|
||||
comment:
|
||||
type: string
|
||||
adjustedShareTx:
|
||||
revertedShareTx:
|
||||
$ref: '#/components/schemas/HsOfficeReferencedCoopSharesTransaction'
|
||||
adjustmentShareTx:
|
||||
reversalShareTx:
|
||||
$ref: '#/components/schemas/HsOfficeReferencedCoopSharesTransaction'
|
||||
|
||||
HsOfficeReferencedCoopSharesTransaction:
|
||||
description:
|
||||
Similar to `HsOfficeCoopSharesTransaction` but without the self-referencing properties
|
||||
(`adjustedShareTx` and `adjustmentShareTx`), to avoid recursive JSON.
|
||||
(`revertedShareTx` and `reversalShareTx`), to avoid recursive JSON.
|
||||
type: object
|
||||
properties:
|
||||
uuid:
|
||||
@ -73,7 +73,7 @@ components:
|
||||
maxLength: 48
|
||||
comment:
|
||||
type: string
|
||||
adjustedShareTx.uuid:
|
||||
revertedShareTx.uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
required:
|
||||
|
@ -2,7 +2,7 @@ get:
|
||||
tags:
|
||||
- hs-office-coopShares
|
||||
description: 'Fetch a single share transaction by its uuid, if visible for the current subject.'
|
||||
operationId: getCoopShareTransactionByUuid
|
||||
operationId: getSingleCoopShareTransactionByUuid
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentSubject'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
|
@ -3,7 +3,7 @@ get:
|
||||
description: Returns the list of (optionally filtered) cooperative share transactions which are visible to the current subject or any of it's assumed roles.
|
||||
tags:
|
||||
- hs-office-coopShares
|
||||
operationId: listCoopShares
|
||||
operationId: getListOfCoopShares
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentSubject'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
@ -46,7 +46,7 @@ post:
|
||||
summary: Adds a new cooperative share transaction.
|
||||
tags:
|
||||
- hs-office-coopShares
|
||||
operationId: addCoopSharesTransaction
|
||||
operationId: postNewCoopSharesTransaction
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentSubject'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
|
@ -28,6 +28,9 @@ create table if not exists hs_office.relation
|
||||
);
|
||||
--//
|
||||
|
||||
-- TODO.impl: unique constraint, to prevent using the same person multiple times as a partner, or better:
|
||||
-- ( anchorUuid, holderUuid, type)
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-relation-MAIN-TABLE-JOURNAL endDelimiter:--//
|
||||
|
@ -4,7 +4,7 @@
|
||||
--changeset michael.hoennig:hs-office-coopshares-MAIN-TABLE endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
CREATE TYPE hs_office.CoopSharesTransactionType AS ENUM ('ADJUSTMENT', 'SUBSCRIPTION', 'CANCELLATION');
|
||||
CREATE TYPE hs_office.CoopSharesTransactionType AS ENUM ('REVERSAL', 'SUBSCRIPTION', 'CANCELLATION');
|
||||
|
||||
CREATE CAST (character varying as hs_office.CoopSharesTransactionType) WITH INOUT AS IMPLICIT;
|
||||
|
||||
@ -17,7 +17,7 @@ create table if not exists hs_office.coopsharetx
|
||||
valueDate date not null,
|
||||
shareCount integer not null,
|
||||
reference varchar(48) not null,
|
||||
adjustedShareTxUuid uuid unique REFERENCES hs_office.coopsharetx(uuid) DEFERRABLE INITIALLY DEFERRED,
|
||||
revertedShareTxUuid uuid unique REFERENCES hs_office.coopsharetx(uuid) DEFERRABLE INITIALLY DEFERRED,
|
||||
comment varchar(512)
|
||||
);
|
||||
--//
|
||||
@ -28,8 +28,8 @@ create table if not exists hs_office.coopsharetx
|
||||
|
||||
alter table hs_office.coopsharetx
|
||||
add constraint reverse_entry_missing
|
||||
check ( transactionType = 'ADJUSTMENT' and adjustedShareTxUuid is not null
|
||||
or transactionType <> 'ADJUSTMENT' and adjustedShareTxUuid is null);
|
||||
check ( transactionType = 'REVERSAL' and revertedShareTxUuid is not null
|
||||
or transactionType <> 'REVERSAL' and revertedShareTxUuid is null);
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
|
@ -27,12 +27,12 @@ begin
|
||||
raise notice 'creating test coopSharesTransaction: %', givenPartnerNumber::text || givenMemberNumberSuffix;
|
||||
subscriptionEntryUuid := uuid_generate_v4();
|
||||
insert
|
||||
into hs_office.coopsharetx(uuid, membershipuuid, transactiontype, valuedate, sharecount, reference, comment, adjustedShareTxUuid)
|
||||
into hs_office.coopsharetx(uuid, membershipuuid, transactiontype, valuedate, sharecount, reference, comment, revertedShareTxUuid)
|
||||
values
|
||||
(uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2010-03-15', 4, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-1', 'initial subscription', null),
|
||||
(uuid_generate_v4(), membership.uuid, 'CANCELLATION', '2021-09-01', -2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-2', 'cancelling some', null),
|
||||
(subscriptionEntryUuid, membership.uuid, 'SUBSCRIPTION', '2022-10-20', 2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-3', 'some subscription', null),
|
||||
(uuid_generate_v4(), membership.uuid, 'ADJUSTMENT', '2022-10-21', -2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-4', 'some adjustment', subscriptionEntryUuid);
|
||||
(uuid_generate_v4(), membership.uuid, 'REVERSAL', '2022-10-21', -2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-4', 'some reversal', subscriptionEntryUuid);
|
||||
end; $$;
|
||||
--//
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
--changeset michael.hoennig:hs-office-coopassets-MAIN-TABLE endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
CREATE TYPE hs_office.CoopAssetsTransactionType AS ENUM ('ADJUSTMENT',
|
||||
CREATE TYPE hs_office.CoopAssetsTransactionType AS ENUM ('REVERSAL',
|
||||
'DEPOSIT',
|
||||
'DISBURSAL',
|
||||
'TRANSFER',
|
||||
@ -22,9 +22,9 @@ create table if not exists hs_office.coopassettx
|
||||
membershipUuid uuid not null references hs_office.membership(uuid),
|
||||
transactionType hs_office.CoopAssetsTransactionType not null,
|
||||
valueDate date not null,
|
||||
assetValue money not null,
|
||||
assetValue numeric(12,2) not null, -- https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_money
|
||||
reference varchar(48) not null,
|
||||
adjustedAssetTxUuid uuid unique REFERENCES hs_office.coopassettx(uuid) DEFERRABLE INITIALLY DEFERRED,
|
||||
revertedAssetTxUuid uuid unique REFERENCES hs_office.coopassettx(uuid) DEFERRABLE INITIALLY DEFERRED,
|
||||
comment varchar(512)
|
||||
);
|
||||
--//
|
||||
@ -36,20 +36,20 @@ create table if not exists hs_office.coopassettx
|
||||
|
||||
alter table hs_office.coopassettx
|
||||
add constraint reverse_entry_missing
|
||||
check ( transactionType = 'ADJUSTMENT' and adjustedAssetTxUuid is not null
|
||||
or transactionType <> 'ADJUSTMENT' and adjustedAssetTxUuid is null);
|
||||
check ( transactionType = 'REVERSAL' and revertedAssetTxUuid is not null
|
||||
or transactionType <> 'REVERSAL' and revertedAssetTxUuid is null);
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-coopassets-ASSET-VALUE-CONSTRAINT endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
create or replace function hs_office.coopassetstx_check_positive_total(forMembershipUuid UUID, newAssetValue money)
|
||||
create or replace function hs_office.coopassetstx_check_positive_total(forMembershipUuid UUID, newAssetValue numeric(12, 5))
|
||||
returns boolean
|
||||
language plpgsql as $$
|
||||
declare
|
||||
currentAssetValue money;
|
||||
totalAssetValue money;
|
||||
currentAssetValue numeric(12,2);
|
||||
totalAssetValue numeric(12,2);
|
||||
begin
|
||||
select sum(cat.assetValue)
|
||||
from hs_office.coopassettx cat
|
||||
|
@ -27,12 +27,12 @@ begin
|
||||
raise notice 'creating test coopAssetsTransaction: %', givenPartnerNumber || givenMemberNumberSuffix;
|
||||
lossEntryUuid := uuid_generate_v4();
|
||||
insert
|
||||
into hs_office.coopassettx(uuid, membershipuuid, transactiontype, valuedate, assetvalue, reference, comment, adjustedAssetTxUuid)
|
||||
into hs_office.coopassettx(uuid, membershipuuid, transactiontype, valuedate, assetvalue, reference, comment, revertedAssetTxUuid)
|
||||
values
|
||||
(uuid_generate_v4(), membership.uuid, 'DEPOSIT', '2010-03-15', 320.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-1', 'initial deposit', null),
|
||||
(uuid_generate_v4(), membership.uuid, 'DISBURSAL', '2021-09-01', -128.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-2', 'partial disbursal', null),
|
||||
(lossEntryUuid, membership.uuid, 'DEPOSIT', '2022-10-20', 128.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-3', 'some loss', null),
|
||||
(uuid_generate_v4(), membership.uuid, 'ADJUSTMENT', '2022-10-21', -128.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-3', 'some adjustment', lossEntryUuid);
|
||||
(uuid_generate_v4(), membership.uuid, 'REVERSAL', '2022-10-21', -128.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-3', 'some reversal', lossEntryUuid);
|
||||
end; $$;
|
||||
--//
|
||||
|
||||
|
@ -442,7 +442,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
|
||||
34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, 1002000, for cancellation D),
|
||||
34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, 1002000, for cancellation D),
|
||||
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),
|
||||
35002=CoopAssetsTransaction(M-1909000: 2024-01-20, REVERSAL, -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),
|
||||
@ -795,23 +795,23 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
|
||||
? HsOfficeCoopSharesTransactionType.SUBSCRIPTION
|
||||
: "UNSUBSCRIPTION".equals(rec.getString("action"))
|
||||
? HsOfficeCoopSharesTransactionType.CANCELLATION
|
||||
: HsOfficeCoopSharesTransactionType.ADJUSTMENT
|
||||
: HsOfficeCoopSharesTransactionType.REVERSAL
|
||||
)
|
||||
.shareCount(rec.getInteger("quantity"))
|
||||
.comment(rec.getString("comment"))
|
||||
.reference(member.getMemberNumber().toString())
|
||||
.build();
|
||||
|
||||
if (shareTransaction.getTransactionType() == HsOfficeCoopSharesTransactionType.ADJUSTMENT) {
|
||||
if (shareTransaction.getTransactionType() == HsOfficeCoopSharesTransactionType.REVERSAL) {
|
||||
final var negativeValue = -shareTransaction.getShareCount();
|
||||
final var adjustedShareTx = coopShares.values().stream().filter(a ->
|
||||
a.getTransactionType() != HsOfficeCoopSharesTransactionType.ADJUSTMENT &&
|
||||
final var revertedShareTx = coopShares.values().stream().filter(a ->
|
||||
a.getTransactionType() != HsOfficeCoopSharesTransactionType.REVERSAL &&
|
||||
a.getMembership() == shareTransaction.getMembership() &&
|
||||
a.getShareCount() == negativeValue)
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalStateException(
|
||||
"cannot determine share reverse entry for adjustment " + shareTransaction));
|
||||
shareTransaction.setAdjustedShareTx(adjustedShareTx);
|
||||
"cannot determine share reverse entry for reversal " + shareTransaction));
|
||||
shareTransaction.setRevertedShareTx(revertedShareTx);
|
||||
}
|
||||
coopShares.put(rec.getInteger("member_share_id"), shareTransaction);
|
||||
});
|
||||
@ -837,7 +837,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
|
||||
final var assetTypeMapping = new HashMap<String, HsOfficeCoopAssetsTransactionType>() {
|
||||
|
||||
{
|
||||
put("ADJUSTMENT", HsOfficeCoopAssetsTransactionType.ADJUSTMENT);
|
||||
put("ADJUSTMENT", HsOfficeCoopAssetsTransactionType.REVERSAL);
|
||||
put("HANDOVER", HsOfficeCoopAssetsTransactionType.TRANSFER);
|
||||
put("ADOPTION", HsOfficeCoopAssetsTransactionType.ADOPTION);
|
||||
put("LOSS", HsOfficeCoopAssetsTransactionType.LOSS);
|
||||
@ -865,16 +865,16 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
|
||||
.reference(member.getMemberNumber().toString())
|
||||
.build();
|
||||
|
||||
if (assetTransaction.getTransactionType() == HsOfficeCoopAssetsTransactionType.ADJUSTMENT) {
|
||||
if (assetTransaction.getTransactionType() == HsOfficeCoopAssetsTransactionType.REVERSAL) {
|
||||
final var negativeValue = assetTransaction.getAssetValue().negate();
|
||||
final var adjustedAssetTx = coopAssets.values().stream().filter(a ->
|
||||
a.getTransactionType() != HsOfficeCoopAssetsTransactionType.ADJUSTMENT &&
|
||||
final var revertedAssetTx = coopAssets.values().stream().filter(a ->
|
||||
a.getTransactionType() != HsOfficeCoopAssetsTransactionType.REVERSAL &&
|
||||
a.getMembership() == assetTransaction.getMembership() &&
|
||||
a.getAssetValue().equals(negativeValue))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalStateException(
|
||||
"cannot determine asset reverse entry for adjustment " + assetTransaction));
|
||||
assetTransaction.setAdjustedAssetTx(adjustedAssetTx);
|
||||
"cannot determine asset reverse entry for reversal " + assetTransaction));
|
||||
assetTransaction.setRevertedAssetTx(revertedAssetTx);
|
||||
}
|
||||
|
||||
coopAssets.put(rec.getInteger("member_asset_id"), assetTransaction);
|
||||
|
@ -55,7 +55,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
|
||||
EntityManager em;
|
||||
|
||||
@Nested
|
||||
class ListCoopAssetsTransactions {
|
||||
class GetListOfCoopAssetsTransactions {
|
||||
|
||||
@Test
|
||||
void globalAdmin_canViewAllCoopAssetsTransactions() {
|
||||
@ -109,21 +109,21 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
|
||||
"valueDate": "2022-10-20",
|
||||
"reference": "ref 1000202-3",
|
||||
"comment": "some loss",
|
||||
"adjustmentAssetTx": {
|
||||
"transactionType": "ADJUSTMENT",
|
||||
"reversalAssetTx": {
|
||||
"transactionType": "REVERSAL",
|
||||
"assetValue": -128.00,
|
||||
"valueDate": "2022-10-21",
|
||||
"reference": "ref 1000202-3",
|
||||
"comment": "some adjustment"
|
||||
"comment": "some reversal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"transactionType": "ADJUSTMENT",
|
||||
"transactionType": "REVERSAL",
|
||||
"assetValue": -128.00,
|
||||
"valueDate": "2022-10-21",
|
||||
"reference": "ref 1000202-3",
|
||||
"comment": "some adjustment",
|
||||
"adjustedAssetTx": {
|
||||
"comment": "some reversal",
|
||||
"revertedAssetTx": {
|
||||
"transactionType": "DEPOSIT",
|
||||
"assetValue": 128.00,
|
||||
"valueDate": "2022-10-20",
|
||||
@ -166,10 +166,10 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AddCoopAssetsTransaction {
|
||||
class PostNewCoopAssetTransaction {
|
||||
|
||||
@Test
|
||||
void globalAdmin_canAddCoopAssetsTransaction() {
|
||||
void globalAdmin_canPostNewCoopAssetTransaction() {
|
||||
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101);
|
||||
@ -214,7 +214,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
|
||||
}
|
||||
|
||||
@Test
|
||||
void globalAdmin_canAddCoopAssetsAdjustmentTransaction() {
|
||||
void globalAdmin_canAddCoopAssetsReversalTransaction() {
|
||||
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101);
|
||||
@ -238,12 +238,12 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
|
||||
.body("""
|
||||
{
|
||||
"membership.uuid": "%s",
|
||||
"transactionType": "ADJUSTMENT",
|
||||
"transactionType": "REVERSAL",
|
||||
"assetValue": %s,
|
||||
"valueDate": "2022-10-30",
|
||||
"reference": "test ref adjustment",
|
||||
"comment": "some coop assets adjustment transaction",
|
||||
"reverseEntry.uuid": "%s"
|
||||
"reference": "test ref reversal",
|
||||
"comment": "some coop assets reversal transaction",
|
||||
"revertedAssetTx.uuid": "%s"
|
||||
}
|
||||
""".formatted(
|
||||
givenMembership.getUuid(),
|
||||
@ -258,12 +258,12 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
|
||||
.body("uuid", isUuidValid())
|
||||
.body("", lenientlyEquals("""
|
||||
{
|
||||
"transactionType": "ADJUSTMENT",
|
||||
"transactionType": "REVERSAL",
|
||||
"assetValue": -256.00,
|
||||
"valueDate": "2022-10-30",
|
||||
"reference": "test ref adjustment",
|
||||
"comment": "some coop assets adjustment transaction",
|
||||
"adjustedAssetTx": {
|
||||
"reference": "test ref reversal",
|
||||
"comment": "some coop assets reversal transaction",
|
||||
"revertedAssetTx": {
|
||||
"transactionType": "DEPOSIT",
|
||||
"assetValue": 256.00,
|
||||
"valueDate": "2022-10-20",
|
||||
|
@ -77,7 +77,7 @@ class HsOfficeCoopAssetsTransactionControllerRestTest {
|
||||
|
||||
ASSETS_VALUE_MUST_NOT_BE_NULL(
|
||||
requestBody -> requestBody
|
||||
.with("transactionType", "ADJUSTMENT")
|
||||
.with("transactionType", "REVERSAL")
|
||||
.with("assetValue", 0.00),
|
||||
"[assetValue must not be 0 but is \"0.00\"]"),
|
||||
|
||||
|
@ -21,14 +21,14 @@ class HsOfficeCoopAssetsTransactionEntityUnitTest {
|
||||
.build();
|
||||
|
||||
|
||||
final HsOfficeCoopAssetsTransactionEntity givenCoopAssetAdjustmentTransaction = HsOfficeCoopAssetsTransactionEntity.builder()
|
||||
final HsOfficeCoopAssetsTransactionEntity givenCoopAssetReversalTransaction = HsOfficeCoopAssetsTransactionEntity.builder()
|
||||
.membership(TEST_MEMBERSHIP)
|
||||
.reference("some-ref")
|
||||
.valueDate(LocalDate.parse("2020-01-15"))
|
||||
.transactionType(HsOfficeCoopAssetsTransactionType.ADJUSTMENT)
|
||||
.transactionType(HsOfficeCoopAssetsTransactionType.REVERSAL)
|
||||
.assetValue(new BigDecimal("-128.00"))
|
||||
.comment("some comment")
|
||||
.adjustedAssetTx(givenCoopAssetTransaction)
|
||||
.revertedAssetTx(givenCoopAssetTransaction)
|
||||
.build();
|
||||
|
||||
final HsOfficeCoopAssetsTransactionEntity givenEmptyCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder().build();
|
||||
@ -41,12 +41,12 @@ class HsOfficeCoopAssetsTransactionEntityUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringWithReverseEntryContainsReverseEntry() {
|
||||
givenCoopAssetTransaction.setAdjustedAssetTx(givenCoopAssetAdjustmentTransaction);
|
||||
void toStringWithRevertedAssetTxContainsRevertedAssetTx() {
|
||||
givenCoopAssetTransaction.setRevertedAssetTx(givenCoopAssetReversalTransaction);
|
||||
|
||||
final var result = givenCoopAssetTransaction.toString();
|
||||
|
||||
assertThat(result).isEqualTo("CoopAssetsTransaction(M-1000101: 2020-01-01, DEPOSIT, 128.00, some-ref, some comment, M-1000101:ADJ:-128.00)");
|
||||
assertThat(result).isEqualTo("CoopAssetsTransaction(M-1000101: 2020-01-01, DEPOSIT, 128.00, some-ref, some comment, M-1000101:REV:-128.00)");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -69,7 +69,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
|
||||
final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder()
|
||||
.membership(givenMembership)
|
||||
.transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT)
|
||||
.assetValue(new BigDecimal("128.00"))
|
||||
.assetValue(new BigDecimal("6400.00"))
|
||||
.valueDate(LocalDate.parse("2022-10-18"))
|
||||
.reference("temp ref A")
|
||||
.build();
|
||||
@ -98,7 +98,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
|
||||
final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder()
|
||||
.membership(givenMembership)
|
||||
.transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT)
|
||||
.assetValue(new BigDecimal("128.00"))
|
||||
.assetValue(new BigDecimal("6400.00"))
|
||||
.valueDate(LocalDate.parse("2022-10-18"))
|
||||
.reference("temp ref B")
|
||||
.build();
|
||||
@ -142,18 +142,18 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
|
||||
result,
|
||||
"CoopAssetsTransaction(M-1000101: 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)",
|
||||
"CoopAssetsTransaction(M-1000101: 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)",
|
||||
"CoopAssetsTransaction(M-1000101: 2022-10-20, DEPOSIT, 128.00, ref 1000101-3, some loss, M-1000101:ADJ:-128.00)",
|
||||
"CoopAssetsTransaction(M-1000101: 2022-10-21, ADJUSTMENT, -128.00, ref 1000101-3, some adjustment, M-1000101:DEP:+128.00)",
|
||||
"CoopAssetsTransaction(M-1000101: 2022-10-20, DEPOSIT, 128.00, ref 1000101-3, some loss, M-1000101:REV:-128.00)",
|
||||
"CoopAssetsTransaction(M-1000101: 2022-10-21, REVERSAL, -128.00, ref 1000101-3, some reversal, M-1000101:DEP:+128.00)",
|
||||
|
||||
"CoopAssetsTransaction(M-1000202: 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)",
|
||||
"CoopAssetsTransaction(M-1000202: 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)",
|
||||
"CoopAssetsTransaction(M-1000202: 2022-10-20, DEPOSIT, 128.00, ref 1000202-3, some loss, M-1000202:ADJ:-128.00)",
|
||||
"CoopAssetsTransaction(M-1000202: 2022-10-21, ADJUSTMENT, -128.00, ref 1000202-3, some adjustment, M-1000202:DEP:+128.00)",
|
||||
"CoopAssetsTransaction(M-1000202: 2022-10-20, DEPOSIT, 128.00, ref 1000202-3, some loss, M-1000202:REV:-128.00)",
|
||||
"CoopAssetsTransaction(M-1000202: 2022-10-21, REVERSAL, -128.00, ref 1000202-3, some reversal, M-1000202:DEP:+128.00)",
|
||||
|
||||
"CoopAssetsTransaction(M-1000303: 2010-03-15, DEPOSIT, 320.00, ref 1000303-1, initial deposit)",
|
||||
"CoopAssetsTransaction(M-1000303: 2021-09-01, DISBURSAL, -128.00, ref 1000303-2, partial disbursal)",
|
||||
"CoopAssetsTransaction(M-1000303: 2022-10-20, DEPOSIT, 128.00, ref 1000303-3, some loss, M-1000303:ADJ:-128.00)",
|
||||
"CoopAssetsTransaction(M-1000303: 2022-10-21, ADJUSTMENT, -128.00, ref 1000303-3, some adjustment, M-1000303:DEP:+128.00)");
|
||||
"CoopAssetsTransaction(M-1000303: 2022-10-20, DEPOSIT, 128.00, ref 1000303-3, some loss, M-1000303:REV:-128.00)",
|
||||
"CoopAssetsTransaction(M-1000303: 2022-10-21, REVERSAL, -128.00, ref 1000303-3, some reversal, M-1000303:DEP:+128.00)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -173,8 +173,8 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
|
||||
result,
|
||||
"CoopAssetsTransaction(M-1000202: 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)",
|
||||
"CoopAssetsTransaction(M-1000202: 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)",
|
||||
"CoopAssetsTransaction(M-1000202: 2022-10-20, DEPOSIT, 128.00, ref 1000202-3, some loss, M-1000202:ADJ:-128.00)",
|
||||
"CoopAssetsTransaction(M-1000202: 2022-10-21, ADJUSTMENT, -128.00, ref 1000202-3, some adjustment, M-1000202:DEP:+128.00)");
|
||||
"CoopAssetsTransaction(M-1000202: 2022-10-20, DEPOSIT, 128.00, ref 1000202-3, some loss, M-1000202:REV:-128.00)",
|
||||
"CoopAssetsTransaction(M-1000202: 2022-10-21, REVERSAL, -128.00, ref 1000202-3, some reversal, M-1000202:DEP:+128.00)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -211,8 +211,8 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
|
||||
result,
|
||||
"CoopAssetsTransaction(M-1000101: 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)",
|
||||
"CoopAssetsTransaction(M-1000101: 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)",
|
||||
"CoopAssetsTransaction(M-1000101: 2022-10-20, DEPOSIT, 128.00, ref 1000101-3, some loss, M-1000101:ADJ:-128.00)",
|
||||
"CoopAssetsTransaction(M-1000101: 2022-10-21, ADJUSTMENT, -128.00, ref 1000101-3, some adjustment, M-1000101:DEP:+128.00)");
|
||||
"CoopAssetsTransaction(M-1000101: 2022-10-20, DEPOSIT, 128.00, ref 1000101-3, some loss, M-1000101:REV:-128.00)",
|
||||
"CoopAssetsTransaction(M-1000101: 2022-10-21, REVERSAL, -128.00, ref 1000101-3, some reversal, M-1000101:DEP:+128.00)");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ListCoopSharesTransactions {
|
||||
class getListOfCoopSharesTransactions {
|
||||
|
||||
@Test
|
||||
void globalAdmin_canViewAllCoopSharesTransactions() {
|
||||
@ -108,21 +108,21 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
|
||||
"valueDate": "2022-10-20",
|
||||
"reference": "ref 1000202-3",
|
||||
"comment": "some subscription",
|
||||
"adjustmentShareTx": {
|
||||
"transactionType": "ADJUSTMENT",
|
||||
"reversalShareTx": {
|
||||
"transactionType": "REVERSAL",
|
||||
"shareCount": -2,
|
||||
"valueDate": "2022-10-21",
|
||||
"reference": "ref 1000202-4",
|
||||
"comment": "some adjustment"
|
||||
"comment": "some reversal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"transactionType": "ADJUSTMENT",
|
||||
"transactionType": "REVERSAL",
|
||||
"shareCount": -2,
|
||||
"valueDate": "2022-10-21",
|
||||
"reference": "ref 1000202-4",
|
||||
"comment": "some adjustment",
|
||||
"adjustedShareTx": {
|
||||
"comment": "some reversal",
|
||||
"revertedShareTx": {
|
||||
"transactionType": "SUBSCRIPTION",
|
||||
"shareCount": 2,
|
||||
"valueDate": "2022-10-20",
|
||||
@ -191,7 +191,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
|
||||
}
|
||||
|
||||
@Test
|
||||
void globalAdmin_canAddCoopSharesAdjustmentTransaction() {
|
||||
void globalAdmin_canAddCoopSharesReversalTransaction() {
|
||||
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101);
|
||||
@ -213,16 +213,16 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
|
||||
.header("current-subject", "superuser-alex@hostsharing.net")
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"membership.uuid": "%s",
|
||||
"transactionType": "ADJUSTMENT",
|
||||
"shareCount": %s,
|
||||
"valueDate": "2022-10-30",
|
||||
"reference": "test ref adjustment",
|
||||
"comment": "some coop shares adjustment transaction",
|
||||
"adjustedShareTx.uuid": "%s"
|
||||
}
|
||||
""".formatted(
|
||||
{
|
||||
"membership.uuid": "%s",
|
||||
"transactionType": "REVERSAL",
|
||||
"shareCount": %s,
|
||||
"valueDate": "2022-10-30",
|
||||
"reference": "test reversal ref",
|
||||
"comment": "some coop shares reversal transaction",
|
||||
"revertedShareTx.uuid": "%s"
|
||||
}
|
||||
""".formatted(
|
||||
givenMembership.getUuid(),
|
||||
-givenTransaction.getShareCount(),
|
||||
givenTransaction.getUuid()))
|
||||
@ -235,12 +235,12 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
|
||||
.body("uuid", isUuidValid())
|
||||
.body("", lenientlyEquals("""
|
||||
{
|
||||
"transactionType": "ADJUSTMENT",
|
||||
"transactionType": "REVERSAL",
|
||||
"shareCount": -13,
|
||||
"valueDate": "2022-10-30",
|
||||
"reference": "test ref adjustment",
|
||||
"comment": "some coop shares adjustment transaction",
|
||||
"adjustedShareTx": {
|
||||
"reference": "test reversal ref",
|
||||
"comment": "some coop shares reversal transaction",
|
||||
"revertedShareTx": {
|
||||
"transactionType": "SUBSCRIPTION",
|
||||
"shareCount": 13,
|
||||
"valueDate": "2022-10-20",
|
||||
|
@ -73,7 +73,7 @@ class HsOfficeCoopSharesTransactionControllerRestTest {
|
||||
|
||||
SHARES_COUNT_MUST_NOT_BE_NULL(
|
||||
requestBody -> requestBody
|
||||
.with("transactionType", "ADJUSTMENT")
|
||||
.with("transactionType", "REVERSAL")
|
||||
.with("shareCount", 0),
|
||||
"[shareCount must not be 0 but is \"0\"]"),
|
||||
|
||||
|
@ -20,14 +20,14 @@ class HsOfficeCoopSharesTransactionEntityUnitTest {
|
||||
.build();
|
||||
|
||||
|
||||
final HsOfficeCoopSharesTransactionEntity givenCoopShareAdjustmentTransaction = HsOfficeCoopSharesTransactionEntity.builder()
|
||||
final HsOfficeCoopSharesTransactionEntity givenCoopShareReversalTransaction = HsOfficeCoopSharesTransactionEntity.builder()
|
||||
.membership(TEST_MEMBERSHIP)
|
||||
.reference("some-ref")
|
||||
.valueDate(LocalDate.parse("2020-01-15"))
|
||||
.transactionType(HsOfficeCoopSharesTransactionType.ADJUSTMENT)
|
||||
.transactionType(HsOfficeCoopSharesTransactionType.REVERSAL)
|
||||
.shareCount(-4)
|
||||
.comment("some comment")
|
||||
.adjustedShareTx(givenCoopSharesTransaction)
|
||||
.revertedShareTx(givenCoopSharesTransaction)
|
||||
.build();
|
||||
|
||||
final HsOfficeCoopSharesTransactionEntity givenEmptyCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder().build();
|
||||
@ -40,12 +40,12 @@ class HsOfficeCoopSharesTransactionEntityUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringWithReverseEntryContainsReverseEntry() {
|
||||
givenCoopSharesTransaction.setAdjustedShareTx(givenCoopShareAdjustmentTransaction);
|
||||
void toStringWithRevertedAssetTxContainsRevertedAssetTx() {
|
||||
givenCoopSharesTransaction.setRevertedShareTx(givenCoopShareReversalTransaction);
|
||||
|
||||
final var result = givenCoopSharesTransaction.toString();
|
||||
|
||||
assertThat(result).isEqualTo("CoopShareTransaction(M-1000101: 2020-01-01, SUBSCRIPTION, 4, some-ref, some comment, M-1000101:ADJ:-4)");
|
||||
assertThat(result).isEqualTo("CoopShareTransaction(M-1000101: 2020-01-01, SUBSCRIPTION, 4, some-ref, some comment, M-1000101:REV:-4)");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -141,18 +141,18 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
|
||||
result,
|
||||
"CoopShareTransaction(M-1000101: 2010-03-15, SUBSCRIPTION, 4, ref 1000101-1, initial subscription)",
|
||||
"CoopShareTransaction(M-1000101: 2021-09-01, CANCELLATION, -2, ref 1000101-2, cancelling some)",
|
||||
"CoopShareTransaction(M-1000101: 2022-10-20, SUBSCRIPTION, 2, ref 1000101-3, some subscription, M-1000101:ADJ:-2)",
|
||||
"CoopShareTransaction(M-1000101: 2022-10-21, ADJUSTMENT, -2, ref 1000101-4, some adjustment, M-1000101:SUB:+2)",
|
||||
"CoopShareTransaction(M-1000101: 2022-10-20, SUBSCRIPTION, 2, ref 1000101-3, some subscription, M-1000101:REV:-2)",
|
||||
"CoopShareTransaction(M-1000101: 2022-10-21, REVERSAL, -2, ref 1000101-4, some reversal, M-1000101:SUB:+2)",
|
||||
|
||||
"CoopShareTransaction(M-1000202: 2010-03-15, SUBSCRIPTION, 4, ref 1000202-1, initial subscription)",
|
||||
"CoopShareTransaction(M-1000202: 2021-09-01, CANCELLATION, -2, ref 1000202-2, cancelling some)",
|
||||
"CoopShareTransaction(M-1000202: 2022-10-20, SUBSCRIPTION, 2, ref 1000202-3, some subscription, M-1000202:ADJ:-2)",
|
||||
"CoopShareTransaction(M-1000202: 2022-10-21, ADJUSTMENT, -2, ref 1000202-4, some adjustment, M-1000202:SUB:+2)",
|
||||
"CoopShareTransaction(M-1000202: 2022-10-20, SUBSCRIPTION, 2, ref 1000202-3, some subscription, M-1000202:REV:-2)",
|
||||
"CoopShareTransaction(M-1000202: 2022-10-21, REVERSAL, -2, ref 1000202-4, some reversal, M-1000202:SUB:+2)",
|
||||
|
||||
"CoopShareTransaction(M-1000303: 2010-03-15, SUBSCRIPTION, 4, ref 1000303-1, initial subscription)",
|
||||
"CoopShareTransaction(M-1000303: 2021-09-01, CANCELLATION, -2, ref 1000303-2, cancelling some)",
|
||||
"CoopShareTransaction(M-1000303: 2022-10-20, SUBSCRIPTION, 2, ref 1000303-3, some subscription, M-1000303:ADJ:-2)",
|
||||
"CoopShareTransaction(M-1000303: 2022-10-21, ADJUSTMENT, -2, ref 1000303-4, some adjustment, M-1000303:SUB:+2)");
|
||||
"CoopShareTransaction(M-1000303: 2022-10-20, SUBSCRIPTION, 2, ref 1000303-3, some subscription, M-1000303:REV:-2)",
|
||||
"CoopShareTransaction(M-1000303: 2022-10-21, REVERSAL, -2, ref 1000303-4, some reversal, M-1000303:SUB:+2)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -172,8 +172,8 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
|
||||
result,
|
||||
"CoopShareTransaction(M-1000202: 2010-03-15, SUBSCRIPTION, 4, ref 1000202-1, initial subscription)",
|
||||
"CoopShareTransaction(M-1000202: 2021-09-01, CANCELLATION, -2, ref 1000202-2, cancelling some)",
|
||||
"CoopShareTransaction(M-1000202: 2022-10-20, SUBSCRIPTION, 2, ref 1000202-3, some subscription, M-1000202:ADJ:-2)",
|
||||
"CoopShareTransaction(M-1000202: 2022-10-21, ADJUSTMENT, -2, ref 1000202-4, some adjustment, M-1000202:SUB:+2)");
|
||||
"CoopShareTransaction(M-1000202: 2022-10-20, SUBSCRIPTION, 2, ref 1000202-3, some subscription, M-1000202:REV:-2)",
|
||||
"CoopShareTransaction(M-1000202: 2022-10-21, REVERSAL, -2, ref 1000202-4, some reversal, M-1000202:SUB:+2)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -210,8 +210,8 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
|
||||
result,
|
||||
"CoopShareTransaction(M-1000101: 2010-03-15, SUBSCRIPTION, 4, ref 1000101-1, initial subscription)",
|
||||
"CoopShareTransaction(M-1000101: 2021-09-01, CANCELLATION, -2, ref 1000101-2, cancelling some)",
|
||||
"CoopShareTransaction(M-1000101: 2022-10-20, SUBSCRIPTION, 2, ref 1000101-3, some subscription, M-1000101:ADJ:-2)",
|
||||
"CoopShareTransaction(M-1000101: 2022-10-21, ADJUSTMENT, -2, ref 1000101-4, some adjustment, M-1000101:SUB:+2)");
|
||||
"CoopShareTransaction(M-1000101: 2022-10-20, SUBSCRIPTION, 2, ref 1000101-3, some subscription, M-1000101:REV:-2)",
|
||||
"CoopShareTransaction(M-1000101: 2022-10-21, REVERSAL, -2, ref 1000101-4, some reversal, M-1000101:SUB:+2)");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
@ -44,8 +45,36 @@ public class HsOfficeMembershipControllerRestTest {
|
||||
@MockBean
|
||||
EntityManagerWrapper em;
|
||||
|
||||
@Nested
|
||||
class GetMemberships {
|
||||
|
||||
@Test
|
||||
void findMembershipByNonExistingMemberNumberReturnsEmptyList() throws Exception {
|
||||
|
||||
// when
|
||||
mockMvc.perform(MockMvcRequestBuilders
|
||||
.get("/api/hs/office/memberships?memberNumber=12345")
|
||||
.header("current-subject", "superuser-alex@hostsharing.net")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"partner.uuid": null,
|
||||
"memberNumberSuffix": "01",
|
||||
"validFrom": "2022-10-13",
|
||||
"membershipFeeBillable": "true"
|
||||
}
|
||||
""")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
|
||||
// then
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("$", hasSize(0)));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AddMembership {
|
||||
|
||||
@Test
|
||||
void respondBadRequest_ifPartnerUuidIsMissing() throws Exception {
|
||||
|
||||
@ -98,7 +127,9 @@ public class HsOfficeMembershipControllerRestTest {
|
||||
.andExpect(status().is4xxClientError())
|
||||
.andExpect(jsonPath("statusCode", is(400)))
|
||||
.andExpect(jsonPath("statusPhrase", is("Bad Request")))
|
||||
.andExpect(jsonPath("message", is("ERROR: [400] Unable to find Partner by partner.uuid: " + givenPartnerUuid)));
|
||||
.andExpect(jsonPath(
|
||||
"message",
|
||||
is("ERROR: [400] Unable to find Partner by partner.uuid: " + givenPartnerUuid)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -13,6 +13,12 @@ import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DontDeleteDefaultDe
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.InvalidateSepaMandateForDebitor;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CancelMembership;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateMembership;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets.CreateCoopAssetsDepositTransaction;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets.CreateCoopAssetsDisbursalTransaction;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets.CreateCoopAssetsRevertTransaction;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares.CreateCoopSharesCancellationTransaction;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares.CreateCoopSharesRevertTransaction;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares.CreateCoopSharesSubscriptionTransaction;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddOperationsContactToPartner;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.partner.CreatePartner;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DeleteDebitor;
|
||||
@ -49,7 +55,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(1010)
|
||||
@Produces(explicitly = "Partner: Test AG", implicitly = {"Person: Test AG", "Contact: Test AG - Hamburg"})
|
||||
@Produces(explicitly = "Partner: P-31010 - Test AG", implicitly = {"Person: Test AG", "Contact: Test AG - Hamburg"})
|
||||
void shouldCreateLegalPersonAsPartner() {
|
||||
new CreatePartner(this)
|
||||
.given("partnerNumber", 31010)
|
||||
@ -71,7 +77,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(1011)
|
||||
@Produces(explicitly = "Partner: Michelle Matthieu", implicitly = {"Person: Michelle Matthieu", "Contact: Michelle Matthieu"})
|
||||
@Produces(explicitly = "Partner: P-31011 - Michelle Matthieu", implicitly = {"Person: Michelle Matthieu", "Contact: Michelle Matthieu"})
|
||||
void shouldCreateNaturalPersonAsPartner() {
|
||||
new CreatePartner(this)
|
||||
.given("partnerNumber", 31011)
|
||||
@ -148,7 +154,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(1100)
|
||||
@Requires("Partner: Michelle Matthieu")
|
||||
@Requires("Partner: P-31011 - Michelle Matthieu")
|
||||
void shouldAmendContactData() {
|
||||
new AmendContactData(this)
|
||||
.given("partnerName", "Matthieu")
|
||||
@ -158,7 +164,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(1101)
|
||||
@Requires("Partner: Michelle Matthieu")
|
||||
@Requires("Partner: P-31011 - Michelle Matthieu")
|
||||
void shouldAddPhoneNumberToContactData() {
|
||||
new AddPhoneNumberToContactData(this)
|
||||
.given("partnerName", "Matthieu")
|
||||
@ -169,7 +175,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(1102)
|
||||
@Requires("Partner: Michelle Matthieu")
|
||||
@Requires("Partner: P-31011 - Michelle Matthieu")
|
||||
void shouldRemovePhoneNumberFromContactData() {
|
||||
new RemovePhoneNumberFromContactData(this)
|
||||
.given("partnerName", "Matthieu")
|
||||
@ -179,7 +185,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(1103)
|
||||
@Requires("Partner: Test AG")
|
||||
@Requires("Partner: P-31010 - Test AG")
|
||||
void shouldReplaceContactData() {
|
||||
new ReplaceContactData(this)
|
||||
.given("partnerName", "Test AG")
|
||||
@ -201,7 +207,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(1201)
|
||||
@Requires("Partner: Michelle Matthieu")
|
||||
@Requires("Partner: P-31011 - Michelle Matthieu")
|
||||
void shouldUpdatePersonData() {
|
||||
new ShouldUpdatePersonData(this)
|
||||
.given("oldFamilyName", "Matthieu")
|
||||
@ -211,7 +217,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(2010)
|
||||
@Requires("Partner: Test AG")
|
||||
@Requires("Partner: P-31010 - Test AG")
|
||||
@Produces("Debitor: Test AG - main debitor")
|
||||
void shouldCreateSelfDebitorForPartner() {
|
||||
new CreateSelfDebitorForPartner(this, "Debitor: Test AG - main debitor")
|
||||
@ -261,18 +267,18 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(2020)
|
||||
@Requires("Debitor: Test AG - main debitor")
|
||||
@Requires("Debitor: D-3101000 - Test AG - main debitor")
|
||||
@Disabled("see TODO.spec in DontDeleteDefaultDebitor")
|
||||
void shouldNotDeleteDefaultDebitor() {
|
||||
new DontDeleteDefaultDebitor(this)
|
||||
.given("partnerNumber", 31020)
|
||||
.given("partnerNumber", 31010)
|
||||
.given("debitorSuffix", "00")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3100)
|
||||
@Requires("Debitor: Test AG - main debitor")
|
||||
@Requires("Debitor: D-3101000 - Test AG - main debitor")
|
||||
@Produces("SEPA-Mandate: Test AG")
|
||||
void shouldCreateSepaMandateForDebitor() {
|
||||
new CreateSepaMandateForDebitor(this)
|
||||
@ -313,12 +319,11 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(4000)
|
||||
@Requires("Partner: Test AG")
|
||||
@Produces("Membership: Test AG 00")
|
||||
@Requires("Partner: P-31010 - Test AG")
|
||||
@Produces("Membership: M-3101000 - Test AG")
|
||||
void shouldCreateMembershipForPartner() {
|
||||
new CreateMembership(this)
|
||||
.given("partnerName", "Test AG")
|
||||
.given("memberNumberSuffix", "00")
|
||||
.given("validFrom", "2024-10-15")
|
||||
.given("newStatus", "ACTIVE")
|
||||
.given("membershipFeeBillable", "true")
|
||||
@ -326,9 +331,87 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
.keep();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4201)
|
||||
@Requires("Membership: M-3101000 - Test AG")
|
||||
@Produces("Coop-Shares SUBSCRIPTION Transaction")
|
||||
void shouldSubscribeCoopShares() {
|
||||
new CreateCoopSharesSubscriptionTransaction(this)
|
||||
.given("memberNumber", "3101000")
|
||||
.given("reference", "sign 2024-01-15")
|
||||
.given("shareCount", 100)
|
||||
.given("comment", "Signing the Membership")
|
||||
.given("transactionDate", "2024-01-15")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4202)
|
||||
@Requires("Membership: M-3101000 - Test AG")
|
||||
void shouldRevertCoopSharesSubscription() {
|
||||
new CreateCoopSharesRevertTransaction(this)
|
||||
.given("memberNumber", "3101000")
|
||||
.given("comment", "reverting some incorrect transaction")
|
||||
.given("dateOfIncorrectTransaction", "2024-02-15")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4202)
|
||||
@Requires("Coop-Shares SUBSCRIPTION Transaction")
|
||||
@Produces("Coop-Shares CANCELLATION Transaction")
|
||||
void shouldCancelCoopSharesSubscription() {
|
||||
new CreateCoopSharesCancellationTransaction(this)
|
||||
.given("memberNumber", "3101000")
|
||||
.given("reference", "cancel 2024-01-15")
|
||||
.given("sharesToCancel", 8)
|
||||
.given("comment", "Cancelling 8 Shares")
|
||||
.given("transactionDate", "2024-02-15")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4301)
|
||||
@Requires("Membership: M-3101000 - Test AG")
|
||||
@Produces("Coop-Assets DEPOSIT Transaction")
|
||||
void shouldSubscribeCoopAssets() {
|
||||
new CreateCoopAssetsDepositTransaction(this)
|
||||
.given("memberNumber", "3101000")
|
||||
.given("reference", "sign 2024-01-15")
|
||||
.given("assetValue", 100*64)
|
||||
.given("comment", "disposal for initial shares")
|
||||
.given("transactionDate", "2024-01-15")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4302)
|
||||
@Requires("Membership: M-3101000 - Test AG")
|
||||
void shouldRevertCoopAssetsSubscription() {
|
||||
new CreateCoopAssetsRevertTransaction(this)
|
||||
.given("memberNumber", "3101000")
|
||||
.given("comment", "reverting some incorrect transaction")
|
||||
.given("dateOfIncorrectTransaction", "2024-02-15")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4302)
|
||||
@Requires("Coop-Assets DEPOSIT Transaction")
|
||||
@Produces("Coop-Assets DISBURSAL Transaction")
|
||||
void shouldDisburseCoopAssets() {
|
||||
new CreateCoopAssetsDisbursalTransaction(this)
|
||||
.given("memberNumber", "3101000")
|
||||
.given("reference", "cancel 2024-01-15")
|
||||
.given("valueToDisburse", 8*64)
|
||||
.given("comment", "disbursal according to shares cancellation")
|
||||
.given("transactionDate", "2024-02-15")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4900)
|
||||
@Requires("Membership: Test AG 00")
|
||||
@Requires("Membership: M-3101000 - Test AG")
|
||||
void shouldCancelMembershipOfPartner() {
|
||||
new CancelMembership(this)
|
||||
.given("memberNumber", "3101000")
|
||||
|
@ -4,6 +4,9 @@ import net.hostsharing.hsadminng.hs.office.scenarios.UseCase.HttpResponse;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class PathAssertion {
|
||||
|
||||
private final String path;
|
||||
@ -14,10 +17,35 @@ public class PathAssertion {
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public Consumer<UseCase.HttpResponse> contains(final String resolvableValue) {
|
||||
return response -> response.path(path).contains(ScenarioTest.resolve(resolvableValue));
|
||||
return response -> {
|
||||
try {
|
||||
response.path(path).map(this::asString).contains(ScenarioTest.resolve(resolvableValue, DROP_COMMENTS));
|
||||
} catch (final AssertionError e) {
|
||||
// without this, the error message is often lacking important context
|
||||
fail(e.getMessage() + " in `path(\"" + path + "\").contains(\"" + resolvableValue + "\")`" );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Consumer<HttpResponse> doesNotExist() {
|
||||
return response -> response.path(path).isNull(); // here, null Optional means key not found in JSON
|
||||
return response -> {
|
||||
try {
|
||||
response.path(path).isNull(); // here, null Optional means key not found in JSON
|
||||
} catch (final AssertionError e) {
|
||||
// without this, the error message is often lacking important context
|
||||
fail(e.getMessage() + " in `path(\"" + path + "\").doesNotExist()`" );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String asString(final Object value) {
|
||||
if (value instanceof Double doubleValue) {
|
||||
if (doubleValue % 1 == 0) {
|
||||
return String.valueOf(doubleValue.intValue()); // avoid trailing ".0"
|
||||
} else {
|
||||
return doubleValue.toString();
|
||||
}
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver;
|
||||
import net.hostsharing.hsadminng.lambda.Reducer;
|
||||
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
@ -26,6 +27,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS;
|
||||
import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public abstract class ScenarioTest extends ContextBasedTest {
|
||||
@ -38,11 +41,11 @@ public abstract class ScenarioTest extends ContextBasedTest {
|
||||
public String toString() {
|
||||
return ObjectUtils.toString(uuid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final static Map<String, Alias<?>> aliases = new HashMap<>();
|
||||
private final static Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
private final static Map<String, Object> properties = new HashMap<>();
|
||||
public final TestReport testReport = new TestReport(aliases);
|
||||
|
||||
@LocalServerPort
|
||||
@ -139,9 +142,9 @@ public abstract class ScenarioTest extends ContextBasedTest {
|
||||
}
|
||||
|
||||
static UUID uuid(final String nameWithPlaceholders) {
|
||||
final var resoledName = resolve(nameWithPlaceholders);
|
||||
final UUID alias = ofNullable(knowVariables().get(resoledName)).filter(v -> v instanceof UUID).map(UUID.class::cast).orElse(null);
|
||||
assertThat(alias).as("alias '" + resoledName + "' not found in aliases nor in properties [" +
|
||||
final var resolvedName = resolve(nameWithPlaceholders, DROP_COMMENTS);
|
||||
final UUID alias = ofNullable(knowVariables().get(resolvedName)).filter(v -> v instanceof UUID).map(UUID.class::cast).orElse(null);
|
||||
assertThat(alias).as("alias '" + resolvedName + "' not found in aliases nor in properties [" +
|
||||
knowVariables().keySet().stream().map(v -> "'" + v + "'").collect(Collectors.joining(", ")) + "]"
|
||||
).isNotNull();
|
||||
return alias;
|
||||
@ -162,13 +165,13 @@ public abstract class ScenarioTest extends ContextBasedTest {
|
||||
return map;
|
||||
}
|
||||
|
||||
public static String resolve(final String text) {
|
||||
final var resolved = new TemplateResolver(text, ScenarioTest.knowVariables()).resolve();
|
||||
public static String resolve(final String text, final Resolver resolver) {
|
||||
final var resolved = new TemplateResolver(text, ScenarioTest.knowVariables()).resolve(resolver);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public static Object resolveTyped(final String text) {
|
||||
final var resolved = resolve(text);
|
||||
final var resolved = resolve(text, DROP_COMMENTS);
|
||||
try {
|
||||
return UUID.fromString(resolved);
|
||||
} catch (final IllegalArgumentException e) {
|
||||
|
@ -10,29 +10,39 @@ import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS;
|
||||
|
||||
public class TemplateResolver {
|
||||
|
||||
private final static Pattern pattern = Pattern.compile(",(\\s*})", Pattern.MULTILINE);
|
||||
private static final String IF_NOT_FOUND_SYMBOL = "???";
|
||||
public enum Resolver {
|
||||
DROP_COMMENTS, // deletes comments ('#{whatever}' -> '')
|
||||
KEEP_COMMENTS // keep comments ('#{whatever}' -> 'whatever')
|
||||
}
|
||||
|
||||
enum PlaceholderPrefix {
|
||||
RAW('%') {
|
||||
@Override
|
||||
String convert(final Object value) {
|
||||
String convert(final Object value, final Resolver resolver) {
|
||||
return value != null ? value.toString() : "";
|
||||
}
|
||||
},
|
||||
JSON_QUOTED('$'){
|
||||
@Override
|
||||
String convert(final Object value) {
|
||||
String convert(final Object value, final Resolver resolver) {
|
||||
return jsonQuoted(value);
|
||||
}
|
||||
},
|
||||
URI_ENCODED('&'){
|
||||
@Override
|
||||
String convert(final Object value) {
|
||||
String convert(final Object value, final Resolver resolver) {
|
||||
return value != null ? URLEncoder.encode(value.toString(), StandardCharsets.UTF_8) : "";
|
||||
}
|
||||
},
|
||||
COMMENT('#'){
|
||||
@Override
|
||||
String convert(final Object value, final Resolver resolver) {
|
||||
return resolver == DROP_COMMENTS ? "" : value.toString();
|
||||
}
|
||||
};
|
||||
|
||||
private final char prefixChar;
|
||||
@ -42,19 +52,24 @@ public class TemplateResolver {
|
||||
}
|
||||
|
||||
static boolean contains(final char givenChar) {
|
||||
return Arrays.stream(values()).anyMatch(p -> p.prefixChar == givenChar);
|
||||
return Arrays.stream(values()).anyMatch(p -> p.prefixChar == givenChar);
|
||||
}
|
||||
|
||||
static PlaceholderPrefix ofPrefixChar(final char givenChar) {
|
||||
return Arrays.stream(values()).filter(p -> p.prefixChar == givenChar).findFirst().orElseThrow();
|
||||
}
|
||||
|
||||
abstract String convert(final Object value);
|
||||
abstract String convert(final Object value, final Resolver resolver);
|
||||
}
|
||||
|
||||
private static final Pattern COMMA_RIGHT_BEFORE_CLOSING_BRACE = Pattern.compile(",(\\s*})", Pattern.MULTILINE);
|
||||
private static final String IF_NOT_FOUND_SYMBOL = "???";
|
||||
|
||||
private final String template;
|
||||
private final Map<String, Object> properties;
|
||||
private final StringBuilder resolved = new StringBuilder();
|
||||
|
||||
private Resolver resolver;
|
||||
private int position = 0;
|
||||
|
||||
public TemplateResolver(final String template, final Map<String, Object> properties) {
|
||||
@ -62,7 +77,8 @@ public class TemplateResolver {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
String resolve() {
|
||||
String resolve(final Resolver resolver) {
|
||||
this.resolver = resolver;
|
||||
final var resolved = copy();
|
||||
final var withoutDroppedLines = dropLinesWithNullProperties(resolved);
|
||||
final var result = removeDanglingCommas(withoutDroppedLines);
|
||||
@ -70,7 +86,7 @@ public class TemplateResolver {
|
||||
}
|
||||
|
||||
private static String removeDanglingCommas(final String withoutDroppedLines) {
|
||||
return pattern.matcher(withoutDroppedLines).replaceAll("$1");
|
||||
return COMMA_RIGHT_BEFORE_CLOSING_BRACE.matcher(withoutDroppedLines).replaceAll("$1");
|
||||
}
|
||||
|
||||
private String dropLinesWithNullProperties(final String text) {
|
||||
@ -119,10 +135,10 @@ public class TemplateResolver {
|
||||
placeholder.append(fetchChar());
|
||||
}
|
||||
}
|
||||
final var name = new TemplateResolver(placeholder.toString(), properties).resolve();
|
||||
final var value = propVal(name);
|
||||
final var content = new TemplateResolver(placeholder.toString(), properties).resolve(resolver);
|
||||
final var value = intro != '#' ? propVal(content) : content;
|
||||
resolved.append(
|
||||
PlaceholderPrefix.ofPrefixChar(intro).convert(value)
|
||||
PlaceholderPrefix.ofPrefixChar(intro).convert(value, resolver)
|
||||
);
|
||||
skipChar('}');
|
||||
}
|
||||
@ -134,12 +150,12 @@ public class TemplateResolver {
|
||||
} else if (nameExpression.contains(IF_NOT_FOUND_SYMBOL)) {
|
||||
final var parts = StringUtils.split(nameExpression, IF_NOT_FOUND_SYMBOL);
|
||||
return Arrays.stream(parts).filter(Objects::nonNull).findFirst().orElseGet(() -> {
|
||||
if ( parts[parts.length-1].isEmpty() ) {
|
||||
// => whole expression ends with IF_NOT_FOUND_SYMBOL, thus last null element was optional
|
||||
return null;
|
||||
}
|
||||
// => last alternative element in expression was null and not optional
|
||||
throw new IllegalStateException("Missing required value in property-chain: " + nameExpression);
|
||||
if ( parts[parts.length-1].isEmpty() ) {
|
||||
// => whole expression ends with IF_NOT_FOUND_SYMBOL, thus last null element was optional
|
||||
return null;
|
||||
}
|
||||
// => last alternative element in expression was null and not optional
|
||||
throw new IllegalStateException("Missing required value in property-chain: " + nameExpression);
|
||||
});
|
||||
} else {
|
||||
final var val = properties.get(nameExpression);
|
||||
|
@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TemplateResolverUnitTest {
|
||||
@ -42,7 +43,7 @@ class TemplateResolverUnitTest {
|
||||
Map.entry("simple placeholder", "einfach"),
|
||||
Map.entry("nested placeholder", "verschachtelt"),
|
||||
Map.entry("with-special-chars", "3&3 AG")
|
||||
)).resolve();
|
||||
)).resolve(DROP_COMMENTS);
|
||||
|
||||
assertThat(resolved).isEqualTo("""
|
||||
with optional JSON quotes:
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.system.SystemProcess;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
@ -9,29 +11,41 @@ import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestReport {
|
||||
|
||||
private final Map<String, ?> aliases;
|
||||
private final StringBuilder markdownLog = new StringBuilder(); // records everything for debugging purposes
|
||||
public static final File BUILD_DOC_SCENARIOS = new File("build/doc/scenarios");
|
||||
private final static File markdownLogFile = new File(BUILD_DOC_SCENARIOS, ".last-debug-log.md");
|
||||
public static final SimpleDateFormat MM_DD_YYYY_HH_MM_SS = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss");
|
||||
|
||||
private PrintWriter markdownReport;
|
||||
private final Map<String, ?> aliases;
|
||||
private final PrintWriter markdownLog; // records everything for debugging purposes
|
||||
private File markdownReportFile;
|
||||
private PrintWriter markdownReport; // records only the use-case under test, without its pre-requisites
|
||||
private int silent; // do not print anything to test-report if >0
|
||||
|
||||
static {
|
||||
assertThat(BUILD_DOC_SCENARIOS.isDirectory() || BUILD_DOC_SCENARIOS.mkdirs())
|
||||
.as("mkdir " + BUILD_DOC_SCENARIOS).isTrue();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public TestReport(final Map<String, ?> aliases) {
|
||||
this.aliases = aliases;
|
||||
this.markdownLog = new PrintWriter(new FileWriter(markdownLogFile));
|
||||
}
|
||||
|
||||
public void createTestLogMarkdownFile(final TestInfo testInfo) throws IOException {
|
||||
final var testMethodName = testInfo.getTestMethod().map(Method::getName).orElseThrow();
|
||||
final var testMethodOrder = testInfo.getTestMethod().map(m -> m.getAnnotation(Order.class).value()).orElseThrow();
|
||||
assertThat(new File("doc/scenarios/").isDirectory() || new File("doc/scenarios/").mkdirs()).as("mkdir doc/scenarios/").isTrue();
|
||||
markdownReport = new PrintWriter(new FileWriter("doc/scenarios/" + testMethodOrder + "-" + testMethodName + ".md"));
|
||||
print("## Scenario #" + testInfo.getTestMethod().map(TestReport::orderNumber).orElseThrow() + ": " +
|
||||
testMethodName.replaceAll("([a-z])([A-Z]+)", "$1 $2"));
|
||||
markdownReportFile = new File(BUILD_DOC_SCENARIOS, testMethodOrder + "-" + testMethodName + ".md");
|
||||
markdownReport = new PrintWriter(new FileWriter(markdownReportFile));
|
||||
print("## Scenario #" + determineScenarioTitle(testInfo));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@ -45,7 +59,7 @@ public class TestReport {
|
||||
}
|
||||
|
||||
// but the debugLog should contain all output, even if silent
|
||||
markdownLog.append(outputWithCommentsForUuids);
|
||||
markdownLog.print(outputWithCommentsForUuids);
|
||||
}
|
||||
|
||||
public void printLine(final String output) {
|
||||
@ -56,10 +70,32 @@ public class TestReport {
|
||||
printLine("\n" +output + "\n");
|
||||
}
|
||||
|
||||
void silent(final Runnable code) {
|
||||
silent++;
|
||||
code.run();
|
||||
silent--;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (markdownReport != null) {
|
||||
printPara("---");
|
||||
printPara("generated on " + MM_DD_YYYY_HH_MM_SS.format(new Date()) + " for branch " + currentGitBranch());
|
||||
markdownReport.close();
|
||||
System.out.println("SCENARIO REPORT: " + asClickableLink(markdownReportFile));
|
||||
}
|
||||
markdownLog.close();
|
||||
System.out.println("DEBUG LOG: " + asClickableLink(markdownLogFile));
|
||||
}
|
||||
|
||||
private static @NotNull String determineScenarioTitle(final TestInfo testInfo) {
|
||||
final var convertedTestMethodName =
|
||||
testInfo.getTestMethod().map(TestReport::orderNumber).orElseThrow() + ": " +
|
||||
testInfo.getTestMethod().map(Method::getName).map(t -> t.replaceAll("([a-z])([A-Z]+)", "$1 $2")).orElseThrow();
|
||||
return convertedTestMethodName.replaceAll(": should ", ": ");
|
||||
}
|
||||
|
||||
private String asClickableLink(final File file) {
|
||||
return file.toURI().toString().replace("file:/", "file:///");
|
||||
}
|
||||
|
||||
private static Object orderNumber(final Method method) {
|
||||
@ -83,10 +119,16 @@ public class TestReport {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
void silent(final Runnable code) {
|
||||
silent++;
|
||||
code.run();
|
||||
silent--;
|
||||
@SneakyThrows
|
||||
private String currentGitBranch() {
|
||||
try {
|
||||
final var gitRevParse = new SystemProcess("git", "rev-parse", "--abbrev-ref", "HEAD");
|
||||
gitRevParse.execute();
|
||||
return gitRevParse.getStdOut().split("\\R", 2)[0];
|
||||
} catch (final IOException exc) {
|
||||
// TODO.test: the git call does not work in Jenkins, we have to find out why
|
||||
System.err.println(exc);
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import com.jayway.jsonpath.PathNotFoundException;
|
||||
import io.restassured.http.ContentType;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
@ -33,6 +34,8 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static java.net.URLEncoder.encode;
|
||||
import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS;
|
||||
import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.KEEP_COMMENTS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.platform.commons.util.StringUtils.isBlank;
|
||||
@ -50,6 +53,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
private final Map<String, Object> givenProperties = new LinkedHashMap<>();
|
||||
|
||||
private String nextTitle; // just temporary to override resultAlias for sub-use-cases
|
||||
private String introduction;
|
||||
|
||||
public UseCase(final ScenarioTest testSuite) {
|
||||
this(testSuite, getResultAliasFromProducesAnnotationInCallStack());
|
||||
@ -71,6 +75,9 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
public final HttpResponse doRun() {
|
||||
if (introduction != null) {
|
||||
testReport.printPara(introduction);
|
||||
}
|
||||
testReport.printPara("### Given Properties");
|
||||
testReport.printLine("""
|
||||
| name | value |
|
||||
@ -81,7 +88,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
testReport.silent(() ->
|
||||
requirements.forEach((alias, factory) -> {
|
||||
if (!ScenarioTest.containsAlias(alias)) {
|
||||
factory.apply(alias).run().keep();
|
||||
factory.apply(alias).run().keepAs(alias);
|
||||
}
|
||||
})
|
||||
);
|
||||
@ -95,6 +102,11 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
protected void verify(final HttpResponse response) {
|
||||
}
|
||||
|
||||
public UseCase<T> introduction(final String introduction) {
|
||||
this.introduction = introduction;
|
||||
return this;
|
||||
}
|
||||
|
||||
public final UseCase<T> given(final String propName, final Object propValue) {
|
||||
givenProperties.put(propName, propValue);
|
||||
ScenarioTest.putProperty(propName, propValue);
|
||||
@ -106,11 +118,11 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
public final void obtain(
|
||||
final String alias,
|
||||
final String title,
|
||||
final Supplier<HttpResponse> http,
|
||||
final Function<HttpResponse, String> extractor,
|
||||
final String... extraInfo) {
|
||||
withTitle(ScenarioTest.resolve(alias), () -> {
|
||||
withTitle(title, () -> {
|
||||
final var response = http.get().keep(extractor);
|
||||
Arrays.stream(extraInfo).forEach(testReport::printPara);
|
||||
return response;
|
||||
@ -118,15 +130,15 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
public final void obtain(final String alias, final Supplier<HttpResponse> http, final String... extraInfo) {
|
||||
withTitle(ScenarioTest.resolve(alias), () -> {
|
||||
withTitle(alias, () -> {
|
||||
final var response = http.get().keep();
|
||||
Arrays.stream(extraInfo).forEach(testReport::printPara);
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
public HttpResponse withTitle(final String title, final Supplier<HttpResponse> code) {
|
||||
this.nextTitle = title;
|
||||
public HttpResponse withTitle(final String resolvableTitle, final Supplier<HttpResponse> code) {
|
||||
this.nextTitle = resolvableTitle;
|
||||
final var response = code.get();
|
||||
this.nextTitle = null;
|
||||
return response;
|
||||
@ -134,7 +146,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpGet(final String uriPathWithPlaceholders) {
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS);
|
||||
final var request = HttpRequest.newBuilder()
|
||||
.GET()
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
@ -147,7 +159,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpPost(final String uriPathWithPlaceholders, final JsonTemplate bodyJsonTemplate) {
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS);
|
||||
final var requestBody = bodyJsonTemplate.resolvePlaceholders();
|
||||
final var request = HttpRequest.newBuilder()
|
||||
.POST(BodyPublishers.ofString(requestBody))
|
||||
@ -162,7 +174,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpPatch(final String uriPathWithPlaceholders, final JsonTemplate bodyJsonTemplate) {
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS);
|
||||
final var requestBody = bodyJsonTemplate.resolvePlaceholders();
|
||||
final var request = HttpRequest.newBuilder()
|
||||
.method(HttpMethod.PATCH.toString(), BodyPublishers.ofString(requestBody))
|
||||
@ -177,7 +189,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpDelete(final String uriPathWithPlaceholders) {
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS);
|
||||
final var request = HttpRequest.newBuilder()
|
||||
.DELETE()
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
@ -197,7 +209,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
final String title,
|
||||
final Supplier<UseCase.HttpResponse> http,
|
||||
final Consumer<UseCase.HttpResponse>... assertions) {
|
||||
withTitle(ScenarioTest.resolve(title), () -> {
|
||||
withTitle(title, () -> {
|
||||
final var response = http.get();
|
||||
Arrays.stream(assertions).forEach(assertion -> assertion.accept(response));
|
||||
return response;
|
||||
@ -209,7 +221,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
public String uriEncoded(final String text) {
|
||||
return encode(ScenarioTest.resolve(text), StandardCharsets.UTF_8);
|
||||
return encode(ScenarioTest.resolve(text, DROP_COMMENTS), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static class JsonTemplate {
|
||||
@ -221,7 +233,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
String resolvePlaceholders() {
|
||||
return ScenarioTest.resolve(template);
|
||||
return ScenarioTest.resolve(template, DROP_COMMENTS);
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,7 +278,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
public HttpResponse keep(final Function<HttpResponse, String> extractor) {
|
||||
final var alias = nextTitle != null ? nextTitle : resultAlias;
|
||||
final var alias = nextTitle != null ? ScenarioTest.resolve(nextTitle, DROP_COMMENTS) : resultAlias;
|
||||
assertThat(alias).as("cannot keep result, no alias found").isNotNull();
|
||||
|
||||
final var value = extractor.apply(this);
|
||||
@ -276,15 +288,20 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponse keep() {
|
||||
final var alias = nextTitle != null ? nextTitle : resultAlias;
|
||||
assertThat(alias).as("cannot keep result, no alias found").isNotNull();
|
||||
public HttpResponse keepAs(final String alias) {
|
||||
ScenarioTest.putAlias(
|
||||
alias,
|
||||
nonNullAlias(alias),
|
||||
new ScenarioTest.Alias<>(UseCase.this.getClass(), locationUuid));
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponse keep() {
|
||||
final var alias = nextTitle != null ? ScenarioTest.resolve(nextTitle, DROP_COMMENTS) : resultAlias;
|
||||
assertThat(alias).as("cannot keep result, no title or alias found for locationUuid: " + locationUuid).isNotNull();
|
||||
|
||||
return keepAs(alias);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public HttpResponse expectArrayElements(final int expectedElementCount) {
|
||||
final var rootNode = objectMapper.readTree(response.body());
|
||||
@ -298,20 +315,20 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
|
||||
@SneakyThrows
|
||||
public String getFromBody(final String path) {
|
||||
return JsonPath.parse(response.body()).read(ScenarioTest.resolve(path));
|
||||
return JsonPath.parse(response.body()).read(ScenarioTest.resolve(path, DROP_COMMENTS));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Optional<String> getFromBodyAsOptional(final String path) {
|
||||
public <T> Optional<T> getFromBodyAsOptional(final String path) {
|
||||
try {
|
||||
return Optional.ofNullable(JsonPath.parse(response.body()).read(ScenarioTest.resolve(path)));
|
||||
} catch (final Exception e) {
|
||||
return Optional.ofNullable(JsonPath.parse(response.body()).read(ScenarioTest.resolve(path, DROP_COMMENTS)));
|
||||
} catch (final PathNotFoundException e) {
|
||||
return null; // means the property did not exist at all, not that it was there with value null
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public OptionalAssert<String> path(final String path) {
|
||||
public <T> OptionalAssert<T> path(final String path) {
|
||||
return assertThat(getFromBodyAsOptional(path));
|
||||
}
|
||||
|
||||
@ -320,9 +337,9 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
|
||||
// the title
|
||||
if (nextTitle != null) {
|
||||
testReport.printLine("\n### " + nextTitle + "\n");
|
||||
testReport.printLine("\n### " + ScenarioTest.resolve(nextTitle, KEEP_COMMENTS) + "\n");
|
||||
} else if (resultAlias != null) {
|
||||
testReport.printLine("\n### " + resultAlias + "\n");
|
||||
testReport.printLine("\n### Create " + resultAlias + "\n");
|
||||
} else {
|
||||
fail("please wrap the http...-call in the UseCase using `withTitle(...)`");
|
||||
}
|
||||
@ -342,6 +359,13 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
testReport.printLine("```");
|
||||
testReport.printLine("");
|
||||
}
|
||||
|
||||
private String nonNullAlias(final String alias) {
|
||||
// This marker tag should not appear in the source-code, as here is nothing to fix.
|
||||
// But if it appears in generated Markdown files, it should show up when that marker tag is searched.
|
||||
final var onlyVisibleInGeneratedMarkdownNotInSource = new String(new char[]{'F', 'I', 'X', 'M', 'E'});
|
||||
return alias == null ? "unknown alias -- " + onlyVisibleInGeneratedMarkdownNotInSource : alias;
|
||||
}
|
||||
}
|
||||
|
||||
protected T self() {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.membership;
|
||||
|
||||
import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
@ -16,10 +16,18 @@ public class CreateMembership extends UseCase<CreateMembership> {
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("Partner: %{partnerName}", () ->
|
||||
httpGet("/api/hs/office/partners?name=&{partnerName}")
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
|
||||
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
return httpPost("/api/hs/office/memberships", usingJsonBody("""
|
||||
{
|
||||
"partner.uuid": ${Partner: Test AG},
|
||||
"memberNumberSuffix": ${memberNumberSuffix},
|
||||
"partner.uuid": ${Partner: %{partnerName}},
|
||||
"memberNumberSuffix": ${%{memberNumberSuffix???}???00},
|
||||
"status": "ACTIVE",
|
||||
"validFrom": ${validFrom},
|
||||
"membershipFeeBillable": ${membershipFeeBillable}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
|
||||
public class CreateCoopAssetsDepositTransaction extends CreateCoopAssetsTransaction {
|
||||
|
||||
public CreateCoopAssetsDepositTransaction(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
|
||||
given("transactionType", "DEPOSIT");
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
|
||||
public class CreateCoopAssetsDisbursalTransaction extends CreateCoopAssetsTransaction {
|
||||
|
||||
public CreateCoopAssetsDisbursalTransaction(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
given("transactionType", "DISBURSAL");
|
||||
given("assetValue", "-%{valueToDisburse}");
|
||||
return super.run();
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
|
||||
public class CreateCoopAssetsRevertTransaction extends CreateCoopAssetsTransaction {
|
||||
|
||||
public CreateCoopAssetsRevertTransaction(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
|
||||
requires("CoopAssets-Transaction with incorrect assetValue", alias ->
|
||||
new CreateCoopAssetsDepositTransaction(testSuite)
|
||||
.given("memberNumber", "3101000")
|
||||
.given("reference", "sign %{dateOfIncorrectTransaction}") // same as revertedAssetTx
|
||||
.given("assetValue", 10)
|
||||
.given("comment", "coop-assets deposit transaction with wrong asset value")
|
||||
.given("transactionDate", "%{dateOfIncorrectTransaction}")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
given("transactionType", "REVERSAL");
|
||||
given("assetValue", -100);
|
||||
given("revertedAssetTx", uuid("CoopAssets-Transaction with incorrect assetValue"));
|
||||
return super.run();
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets;
|
||||
|
||||
import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public abstract class CreateCoopAssetsTransaction extends UseCase<CreateCoopAssetsTransaction> {
|
||||
|
||||
public CreateCoopAssetsTransaction(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("#{Find }membershipUuid", () ->
|
||||
httpGet("/api/hs/office/memberships?memberNumber=&{memberNumber}")
|
||||
.expecting(OK).expecting(JSON).expectArrayElements(1),
|
||||
response -> response.getFromBody("$[0].uuid")
|
||||
);
|
||||
|
||||
return withTitle("Create the Coop-Assets-%{transactionType} Transaction", () ->
|
||||
httpPost("/api/hs/office/coopassetstransactions", usingJsonBody("""
|
||||
{
|
||||
"membership.uuid": ${membershipUuid},
|
||||
"transactionType": ${transactionType},
|
||||
"reference": ${reference},
|
||||
"assetValue": ${assetValue},
|
||||
"comment": ${comment},
|
||||
"valueDate": ${transactionDate},
|
||||
"revertedAssetTx.uuid": ${revertedAssetTx???}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verify(final HttpResponse response) {
|
||||
verify("Verify Coop-Assets %{transactionType}-Transaction",
|
||||
() -> httpGet("/api/hs/office/coopassetstransactions/" + response.getLocationUuid())
|
||||
.expecting(HttpStatus.OK).expecting(ContentType.JSON),
|
||||
path("transactionType").contains("%{transactionType}"),
|
||||
path("assetValue").contains("%{assetValue}"),
|
||||
path("comment").contains("%{comment}"),
|
||||
path("valueDate").contains("%{transactionDate}")
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
|
||||
public class CreateCoopSharesCancellationTransaction extends CreateCoopSharesTransaction {
|
||||
|
||||
public CreateCoopSharesCancellationTransaction(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
given("transactionType", "CANCELLATION");
|
||||
given("shareCount", "-%{sharesToCancel}");
|
||||
return super.run();
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
|
||||
public class CreateCoopSharesRevertTransaction extends CreateCoopSharesTransaction {
|
||||
|
||||
public CreateCoopSharesRevertTransaction(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
|
||||
requires("CoopShares-Transaction with incorrect shareCount", alias ->
|
||||
new CreateCoopSharesSubscriptionTransaction(testSuite)
|
||||
.given("memberNumber", "3101000")
|
||||
.given("reference", "sign %{dateOfIncorrectTransaction}") // same as revertedShareTx
|
||||
.given("shareCount", 100)
|
||||
.given("comment", "coop-shares subscription transaction with wrong share count")
|
||||
.given("transactionDate", "%{dateOfIncorrectTransaction}")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
given("transactionType", "REVERSAL");
|
||||
given("shareCount", -100);
|
||||
given("revertedShareTx", uuid("CoopShares-Transaction with incorrect shareCount"));
|
||||
return super.run();
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
|
||||
public class CreateCoopSharesSubscriptionTransaction extends CreateCoopSharesTransaction {
|
||||
|
||||
public CreateCoopSharesSubscriptionTransaction(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
|
||||
given("transactionType", "SUBSCRIPTION");
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares;
|
||||
|
||||
import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopSharesTransaction> {
|
||||
|
||||
public CreateCoopSharesTransaction(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("#{Find }membershipUuid", () ->
|
||||
httpGet("/api/hs/office/memberships?memberNumber=&{memberNumber}")
|
||||
.expecting(OK).expecting(JSON).expectArrayElements(1),
|
||||
response -> response.getFromBody("$[0].uuid")
|
||||
);
|
||||
|
||||
return withTitle("Create the Coop-Shares-%{transactionType} Transaction", () ->
|
||||
httpPost("/api/hs/office/coopsharestransactions", usingJsonBody("""
|
||||
{
|
||||
"membership.uuid": ${membershipUuid},
|
||||
"transactionType": ${transactionType},
|
||||
"reference": ${reference},
|
||||
"shareCount": ${shareCount},
|
||||
"comment": ${comment},
|
||||
"valueDate": ${transactionDate},
|
||||
"revertedShareTx.uuid": ${revertedShareTx???}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verify(final HttpResponse response) {
|
||||
verify("Verify Coop-Shares %{transactionType}-Transaction",
|
||||
() -> httpGet("/api/hs/office/coopsharestransactions/" + response.getLocationUuid())
|
||||
.expecting(HttpStatus.OK).expecting(ContentType.JSON),
|
||||
path("transactionType").contains("%{transactionType}"),
|
||||
path("shareCount").contains("%{shareCount}"),
|
||||
path("comment").contains("%{comment}"),
|
||||
path("valueDate").contains("%{transactionDate}")
|
||||
);
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@ public class CreatePartner extends UseCase<CreatePartner> {
|
||||
|
||||
public CreatePartner(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
|
||||
introduction("A partner can be a client or a vendor, currently we only use them for clients.");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
Reference in New Issue
Block a user