http-get endpoints for partner, debitor and memberhip-number #135

Merged
39 changed files with 672 additions and 171 deletions

View File

@ -0,0 +1,23 @@
package net.hostsharing.hsadminng.errors;
import lombok.AllArgsConstructor;
import jakarta.validation.ValidationException;
@AllArgsConstructor
public class Validate {
final String variableNames;
public static Validate validate(final String variableNames) {
return new Validate(variableNames);
}
public final void atMaxOneNonNull(final Object var1, final Object var2) {
if (var1 != null && var2 != null) {
throw new ValidationException(
"Exactly one of (" + variableNames + ") must be non-null, " +
"but are (" + var1 + ", " + var2 + ")");
}
}
}

View File

@ -290,11 +290,10 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
if (adoptingMembershipMemberNumber != null) { if (adoptingMembershipMemberNumber != null) {
final var adoptingMemberNumber = Integer.valueOf(adoptingMembershipMemberNumber.substring("M-".length())); final var adoptingMemberNumber = Integer.valueOf(adoptingMembershipMemberNumber.substring("M-".length()));
final var adoptingMembership = membershipRepo.findMembershipByMemberNumber(adoptingMemberNumber); final var adoptingMembership = membershipRepo.findMembershipByMemberNumber(adoptingMemberNumber);
if (adoptingMembership != null) { return adoptingMembership.orElseThrow( () ->
return adoptingMembership; new ValidationException("adoptingMembership.memberNumber='" + adoptingMembershipMemberNumber
} + "' not found or not accessible")
throw new ValidationException("adoptingMembership.memberNumber='" + adoptingMembershipMemberNumber );
+ "' not found or not accessible");
} }
throw new ValidationException( throw new ValidationException(

View File

@ -57,12 +57,15 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final String name, final String name,
final String debitorNumber) { final UUID partnerUuid,
final String partnerNumber) {
context.define(currentSubject, assumedRoles); context.define(currentSubject, assumedRoles);
final var entities = debitorNumber != null final var entities = partnerNumber != null
? debitorRepo.findDebitorByDebitorNumber(cropTag("D-", debitorNumber)) ? debitorRepo.findDebitorsByPartnerNumber(cropTag("P-", partnerNumber))
: debitorRepo.findDebitorByOptionalNameLike(name); : partnerUuid != null
? debitorRepo.findDebitorsByPartnerUuid(partnerUuid)
: debitorRepo.findDebitorsByOptionalNameLike(name);
final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources); return ResponseEntity.ok(resources);
@ -133,6 +136,23 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
return ResponseEntity.ok(mapper.map(result.get(), HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)); return ResponseEntity.ok(mapper.map(result.get(), HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER));
} }
@Override
@Transactional(readOnly = true)
@Timed("app.office.debitors.api.getSingleDebitorByDebitorNumber")
public ResponseEntity<HsOfficeDebitorResource> getSingleDebitorByDebitorNumber(
final String currentSubject,
final String assumedRoles,
final Integer debitorNumber) {
context.define(currentSubject, assumedRoles);
final var result = debitorRepo.findDebitorByDebitorNumber(debitorNumber);
if (result.isEmpty()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(mapper.map(result.get(), HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER));
}
@Override @Override
@Transactional @Transactional
@Timed("app.office.debitors.api.deleteDebitorByUuid") @Timed("app.office.debitors.api.deleteDebitorByUuid")

View File

@ -1,6 +1,7 @@
package net.hostsharing.hsadminng.hs.office.debitor; package net.hostsharing.hsadminng.hs.office.debitor;
import io.micrometer.core.annotation.Timed; import io.micrometer.core.annotation.Timed;
import net.hostsharing.hsadminng.lambda.Reducer;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
@ -13,21 +14,29 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
@Timed("app.office.debitors.repo.findByUuid") @Timed("app.office.debitors.repo.findByUuid")
Optional<HsOfficeDebitorEntity> findByUuid(UUID id); Optional<HsOfficeDebitorEntity> findByUuid(UUID id);
@Timed("app.office.debitors.repo.findDebitorByPartnerUuid")
List<HsOfficeDebitorEntity> findDebitorsByPartnerUuid(UUID partnerUuid);
@Query(""" @Query("""
SELECT debitor FROM HsOfficeDebitorEntity debitor SELECT debitor FROM HsOfficeDebitorEntity debitor
JOIN HsOfficePartnerEntity partner JOIN HsOfficePartnerEntity partner
ON partner.partnerRel.holder = debitor.debitorRel.anchor ON partner.partnerRel.holder = debitor.debitorRel.anchor
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR' AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
WHERE partner.partnerNumber = :partnerNumber WHERE partner.partnerNumber = :partnerNumber
AND debitor.debitorNumberSuffix = :debitorNumberSuffix AND (:debitorNumberSuffix IS NULL OR debitor.debitorNumberSuffix = :debitorNumberSuffix)
""") """)
@Timed("app.office.debitors.repo.findDebitorByPartnerNumberAndDebitorNumberSuffix") @Timed("app.office.debitors.repo.findDebitorByPartnerNumberAndDebitorNumberSuffix")
List<HsOfficeDebitorEntity> findDebitorByPartnerNumberAndDebitorNumberSuffix(int partnerNumber, String debitorNumberSuffix); List<HsOfficeDebitorEntity> findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(int partnerNumber, String debitorNumberSuffix);
default List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int debitorNumber) { default Optional<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int debitorNumber) {
final var partnerNumber = debitorNumber / 100; final var partnerNumber = debitorNumber / 100;
final String suffix = String.format("%02d", debitorNumber % 100); final String suffix = String.format("%02d", debitorNumber % 100);
final var result = findDebitorByPartnerNumberAndDebitorNumberSuffix(partnerNumber, suffix); final var result = findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(partnerNumber, suffix);
return result.stream().reduce(Reducer::toSingleElement);
}
default List<HsOfficeDebitorEntity> findDebitorsByPartnerNumber(int partnerNumber) {
final var result = findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(partnerNumber, null);
return result; return result;
} }
@ -50,7 +59,7 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
OR contact.caption like concat(cast(:name as text), '%') OR contact.caption like concat(cast(:name as text), '%')
""") """)
@Timed("app.office.debitors.repo.findDebitorByOptionalNameLike") @Timed("app.office.debitors.repo.findDebitorByOptionalNameLike")
List<HsOfficeDebitorEntity> findDebitorByOptionalNameLike(String name); List<HsOfficeDebitorEntity> findDebitorsByOptionalNameLike(String name);
@Timed("app.office.debitors.repo.save") @Timed("app.office.debitors.repo.save")
HsOfficeDebitorEntity save(final HsOfficeDebitorEntity entity); HsOfficeDebitorEntity save(final HsOfficeDebitorEntity entity);

View File

@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeMembersh
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.mapper.StandardMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -17,7 +18,7 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.errors.Validate.validate;
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag; import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
@RestController @RestController
@ -39,16 +40,20 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final UUID partnerUuid, final UUID partnerUuid,
final String memberNumber) { final String partnerNumber) {
context.define(currentSubject, assumedRoles); context.define(currentSubject, assumedRoles);
final var entities = (memberNumber != null) validate("partnerUuid, partnerNumber").atMaxOneNonNull(partnerUuid, partnerNumber);
? ofNullable(membershipRepo.findMembershipByMemberNumber(
cropTag(HsOfficeMembershipEntity.MEMBER_NUMBER_TAG, memberNumber))).stream()
.toList()
: membershipRepo.findMembershipsByOptionalPartnerUuid(partnerUuid);
final var resources = mapper.mapList(entities, HsOfficeMembershipResource.class, final var entities = partnerNumber != null
? membershipRepo.findMembershipsByPartnerNumber(
cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, partnerNumber))
: partnerUuid != null
? membershipRepo.findMembershipsByPartnerUuid(partnerUuid)
: membershipRepo.findAll();
final var resources = mapper.mapList(
entities, HsOfficeMembershipResource.class,
SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources); return ResponseEntity.ok(resources);
} }
@ -72,7 +77,8 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
.path("/api/hs/office/memberships/{id}") .path("/api/hs/office/memberships/{id}")
.buildAndExpand(saved.getUuid()) .buildAndExpand(saved.getUuid())
.toUri(); .toUri();
final var mapped = mapper.map(saved, HsOfficeMembershipResource.class, final var mapped = mapper.map(
saved, HsOfficeMembershipResource.class,
SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.created(uri).body(mapped); return ResponseEntity.created(uri).body(mapped);
} }
@ -91,7 +97,27 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
if (result.isEmpty()) { if (result.isEmpty()) {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }
return ResponseEntity.ok(mapper.map(result.get(), HsOfficeMembershipResource.class, return ResponseEntity.ok(mapper.map(
result.get(), HsOfficeMembershipResource.class,
SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER));
}
@Override
@Transactional(readOnly = true)
@Timed("app.office.membership.api.getSingleMembershipByMembershipNumber")
public ResponseEntity<HsOfficeMembershipResource> getSingleMembershipByMembershipNumber(
final String currentSubject,
final String assumedRoles,
final Integer membershipNumber) {
context.define(currentSubject, assumedRoles);
final var result = membershipRepo.findMembershipByMemberNumber(membershipNumber);
if (result.isEmpty()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(mapper.map(
result.get(), HsOfficeMembershipResource.class,
SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER)); SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER));
} }

View File

@ -22,12 +22,19 @@ public interface HsOfficeMembershipRepository extends Repository<HsOfficeMembers
@Query(""" @Query("""
SELECT membership FROM HsOfficeMembershipEntity membership SELECT membership FROM HsOfficeMembershipEntity membership
WHERE ( CAST(:partnerUuid as org.hibernate.type.UUIDCharType) IS NULL WHERE membership.partner.uuid = :partnerUuid
OR membership.partner.uuid = :partnerUuid )
ORDER BY membership.partner.partnerNumber, membership.memberNumberSuffix ORDER BY membership.partner.partnerNumber, membership.memberNumberSuffix
""") """)
@Timed("app.office.membership.repo.findMembershipsByOptionalPartnerUuid") @Timed("app.office.membership.repo.findMembershipsByOptionalPartnerUuid")
List<HsOfficeMembershipEntity> findMembershipsByOptionalPartnerUuid(UUID partnerUuid); List<HsOfficeMembershipEntity> findMembershipsByPartnerUuid(UUID partnerUuid);
@Query("""
SELECT membership FROM HsOfficeMembershipEntity membership
WHERE membership.partner.partnerNumber = :partnerNumber
ORDER BY membership.partner.partnerNumber, membership.memberNumberSuffix
""")
@Timed("app.office.membership.repo.findMembershipsByPartnerNumber")
List<HsOfficeMembershipEntity> findMembershipsByPartnerNumber(Integer partnerNumber);
@Query(""" @Query("""
SELECT membership FROM HsOfficeMembershipEntity membership SELECT membership FROM HsOfficeMembershipEntity membership
@ -35,12 +42,12 @@ public interface HsOfficeMembershipRepository extends Repository<HsOfficeMembers
AND (membership.memberNumberSuffix = :suffix) AND (membership.memberNumberSuffix = :suffix)
ORDER BY membership.memberNumberSuffix ORDER BY membership.memberNumberSuffix
""") """)
@Timed("app.office.membership.repo.findMembershipByPartnerNumberAndSuffix") @Timed("app.office.membership.repo.findMembershipByMemberNumber")
HsOfficeMembershipEntity findMembershipByPartnerNumberAndSuffix( Optional<HsOfficeMembershipEntity> findMembershipByPartnerNumberAndSuffix(
@NotNull Integer partnerNumber, @NotNull Integer partnerNumber,
@NotNull String suffix); @NotNull String suffix);
default HsOfficeMembershipEntity findMembershipByMemberNumber(Integer memberNumber) { default Optional<HsOfficeMembershipEntity> findMembershipByMemberNumber(final Integer memberNumber) {
final var partnerNumber = memberNumber / 100; final var partnerNumber = memberNumber / 100;
final String suffix = String.format("%02d", memberNumber % 100); final String suffix = String.format("%02d", memberNumber % 100);
final var result = findMembershipByPartnerNumberAndSuffix(partnerNumber, suffix); final var result = findMembershipByPartnerNumberAndSuffix(partnerNumber, suffix);

View File

@ -104,6 +104,23 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class)); return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class));
} }
@Override
@Transactional(readOnly = true)
@Timed("app.office.partners.api.getSinglePartnerByPartnerNumber")
public ResponseEntity<HsOfficePartnerResource> getSinglePartnerByPartnerNumber(
final String currentSubject,
final String assumedRoles,
final Integer partnerNumber) {
context.define(currentSubject, assumedRoles);
final var result = partnerRepo.findPartnerByPartnerNumber(partnerNumber);
if (result.isEmpty()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class));
}
@Override @Override
@Transactional @Transactional
@Timed("app.office.partners.api.deletePartnerByUuid") @Timed("app.office.partners.api.deletePartnerByUuid")

View File

@ -32,7 +32,7 @@ public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEnt
List<HsOfficePartnerEntity> findPartnerByOptionalNameLike(String name); List<HsOfficePartnerEntity> findPartnerByOptionalNameLike(String name);
@Timed("app.office.partners.repo.findPartnerByPartnerNumber") @Timed("app.office.partners.repo.findPartnerByPartnerNumber")
HsOfficePartnerEntity findPartnerByPartnerNumber(Integer partnerNumber); Optional<HsOfficePartnerEntity> findPartnerByPartnerNumber(Integer partnerNumber);
@Timed("app.office.partners.repo.save") @Timed("app.office.partners.repo.save")
HsOfficePartnerEntity save(final HsOfficePartnerEntity entity); HsOfficePartnerEntity save(final HsOfficePartnerEntity entity);

View File

@ -13,6 +13,7 @@ map:
- type: string:uuid => java.util.UUID - type: string:uuid => java.util.UUID
- type: string:format => java.lang.String - type: string:format => java.lang.String
- type: number:currency => java.math.BigDecimal - type: number:currency => java.math.BigDecimal
- type: number:integer => java.lang.Integer
paths: paths:
/api/hs/office/partners/{partnerUUID}: /api/hs/office/partners/{partnerUUID}:

View File

@ -27,8 +27,6 @@ components:
nullable: false nullable: false
membership.memberNumber: membership.memberNumber:
type: string type: string
minLength: 9
maxLength: 9
pattern: 'M-[0-9]{7}' pattern: 'M-[0-9]{7}'
transactionType: transactionType:
$ref: '#/components/schemas/HsOfficeCoopAssetsTransactionType' $ref: '#/components/schemas/HsOfficeCoopAssetsTransactionType'
@ -69,8 +67,6 @@ components:
nullable: false nullable: false
membership.memberNumber: membership.memberNumber:
type: string type: string
minLength: 9
maxLength: 9
pattern: 'M-[0-9]{7}' pattern: 'M-[0-9]{7}'
transactionType: transactionType:
$ref: '#/components/schemas/HsOfficeCoopAssetsTransactionType' $ref: '#/components/schemas/HsOfficeCoopAssetsTransactionType'
@ -130,8 +126,6 @@ components:
format: uuid format: uuid
adoptingMembership.memberNumber: adoptingMembership.memberNumber:
type: string type: string
minLength: 9
maxLength: 9
pattern: 'M-[0-9]{7}' pattern: 'M-[0-9]{7}'
required: required:
- membership.uuid - membership.uuid

View File

@ -13,8 +13,6 @@ components:
$ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation' $ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
debitorNumber: debitorNumber:
type: string type: string
minLength: 9
maxLength: 9
pattern: 'D-[0-9]{7}' pattern: 'D-[0-9]{7}'
debitorNumberSuffix: debitorNumberSuffix:
type: string type: string

View File

@ -0,0 +1,29 @@
get:
tags:
- hs-office-debitors
description: 'Fetch a single debitor by its debitorNumber, if visible for the current subject.'
operationId: getSingleDebitorByDebitorNumber
parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
- name: debitorNumber
in: path
required: true
schema:
type: number
format: integer
minimum: 1000000
maximum: 9999999
description: debitor-number of the debitor to fetch.
responses:
"200":
description: OK
content:
'application/json':
schema:
$ref: 'hs-office-debitor-schemas.yaml#/components/schemas/HsOfficeDebitor'
"401":
$ref: 'error-responses.yaml#/components/responses/Unauthorized'
"403":
$ref: 'error-responses.yaml#/components/responses/Forbidden'

View File

@ -13,15 +13,20 @@ get:
schema: schema:
type: string type: string
description: Prefix of name properties from person or contact to filter the results. description: Prefix of name properties from person or contact to filter the results.
- name: debitorNumber - name: partnerUuid
in: query in: query
required: false required: false
schema: schema:
type: string type: string
minLength: 9 format: uuid
maxLength: 9 description: UUID of the business partner, exclusive to `memberNumber`.
pattern: 'D-[0-9]{7}' - name: partnerNumber
description: Debitor number of the requested debitor. in: query
required: false
schema:
type: string
pattern: 'P-[0-9]{5}'
description: Partner number of the requested debitor.
responses: responses:
"200": "200":
description: OK description: OK

View File

@ -27,14 +27,10 @@ components:
$ref: 'hs-office-debitor-schemas.yaml#/components/schemas/HsOfficeDebitor' $ref: 'hs-office-debitor-schemas.yaml#/components/schemas/HsOfficeDebitor'
memberNumber: memberNumber:
type: string type: string
minLength: 9
maxLength: 9
pattern: 'M-[0-9]{7}' pattern: 'M-[0-9]{7}'
memberNumberSuffix: memberNumberSuffix:
type: string type: string
minLength: 2 pattern: '[0-9]{2}'
maxLength: 2
pattern: '[0-9]+'
validFrom: validFrom:
type: string type: string
format: date format: date
@ -69,9 +65,7 @@ components:
nullable: false nullable: false
memberNumberSuffix: memberNumberSuffix:
type: string type: string
minLength: 2 pattern: '[0-9]{2}'
maxLength: 2
pattern: '[0-9]+'
nullable: false nullable: false
validFrom: validFrom:
type: string type: string

View File

@ -0,0 +1,29 @@
get:
tags:
- hs-office-memberships
description: 'Fetch a single membership by its membershipNumber, if visible for the current subject.'
operationId: getSingleMembershipByMembershipNumber
parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
- name: membershipNumber
in: path
required: true
schema:
type: number
format: integer
minimum: 1000000
maximum: 9999999
description: membershipNumber of the membership to fetch.
responses:
"200":
description: OK
content:
'application/json':
schema:
$ref: 'hs-office-membership-schemas.yaml#/components/schemas/HsOfficeMembership'
"401":
$ref: 'error-responses.yaml#/components/responses/Unauthorized'
"403":
$ref: 'error-responses.yaml#/components/responses/Forbidden'

View File

@ -15,15 +15,13 @@ get:
type: string type: string
format: uuid format: uuid
description: UUID of the business partner, exclusive to `memberNumber`. description: UUID of the business partner, exclusive to `memberNumber`.
- name: memberNumber - name: partnerNumber
in: query in: query
required: false required: false
schema: schema:
type: string type: string
minLength: 9 pattern: 'P-[0-9]{5}'
maxLength: 9 description: partnerNumber of the partner the memberships belong to
pattern: 'M-[0-9]{7}'
description: Member number, exclusive to `partnerUuid`.
responses: responses:
"200": "200":
description: OK description: OK

View File

@ -11,8 +11,6 @@ components:
format: uuid format: uuid
partnerNumber: partnerNumber:
type: string type: string
minLength: 7
maxLength: 7
pattern: 'P-[0-9]{5}' pattern: 'P-[0-9]{5}'
partnerRel: partnerRel:
$ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation' $ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
@ -87,8 +85,6 @@ components:
properties: properties:
partnerNumber: partnerNumber:
type: string type: string
minLength: 7
maxLength: 7
pattern: 'P-[0-9]{5}' pattern: 'P-[0-9]{5}'
partnerRel: partnerRel:
$ref: '#/components/schemas/HsOfficePartnerRelInsert' $ref: '#/components/schemas/HsOfficePartnerRelInsert'

View File

@ -0,0 +1,28 @@
get:
tags:
- hs-office-partners
description: 'Fetch a single business partner by its partner-number (prefixed with "P-"), if visible for the current subject.'
operationId: getSinglePartnerByPartnerNumber
parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
- name: partnerNumber
in: path
required: true
schema:
type: integer
minimum: 10000
maximum: 99999
description: partner-number (prefixed with "P-") of the partner to fetch.
responses:
"200":
description: OK
content:
'application/json':
schema:
$ref: 'hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner'
"401":
$ref: 'error-responses.yaml#/components/responses/Unauthorized'
"403":
$ref: 'error-responses.yaml#/components/responses/Forbidden'

View File

@ -13,6 +13,9 @@ paths:
/api/hs/office/partners: /api/hs/office/partners:
$ref: "hs-office-partners.yaml" $ref: "hs-office-partners.yaml"
/api/hs/office/partners/P-{partnerNumber}:
$ref: "hs-office-partners-with-partnerNumber.yaml"
/api/hs/office/partners/{partnerUUID}: /api/hs/office/partners/{partnerUUID}:
$ref: "hs-office-partners-with-uuid.yaml" $ref: "hs-office-partners-with-uuid.yaml"
@ -58,6 +61,9 @@ paths:
/api/hs/office/debitors: /api/hs/office/debitors:
$ref: "hs-office-debitors.yaml" $ref: "hs-office-debitors.yaml"
/api/hs/office/debitors/D-{debitorNumber}:
$ref: "hs-office-debitors-with-debitorNumber.yaml"
/api/hs/office/debitors/{debitorUUID}: /api/hs/office/debitors/{debitorUUID}:
$ref: "hs-office-debitors-with-uuid.yaml" $ref: "hs-office-debitors-with-uuid.yaml"
@ -76,6 +82,9 @@ paths:
/api/hs/office/memberships: /api/hs/office/memberships:
$ref: "hs-office-memberships.yaml" $ref: "hs-office-memberships.yaml"
/api/hs/office/memberships/M-{membershipNumber}:
$ref: "hs-office-memberships-with-membershipNumber.yaml"
/api/hs/office/memberships/{membershipUUID}: /api/hs/office/memberships/{membershipUUID}:
$ref: "hs-office-memberships-with-uuid.yaml" $ref: "hs-office-memberships-with-uuid.yaml"

View File

@ -0,0 +1,31 @@
package net.hostsharing.hsadminng.errors;
import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
class ValidateUnitTest {
@Test
void shouldFailValidationIfBothParametersAreNotNull() {
final var throwable = catchThrowable(() ->
Validate.validate("var1, var2").atMaxOneNonNull("val1", "val2")
);
assertThat(throwable).isInstanceOf(ValidationException.class)
.hasMessage("Exactly one of (var1, var2) must be non-null, but are (val1, val2)");
}
@Test
void shouldNotFailValidationIfBothParametersAreull() {
Validate.validate("var1, var2").atMaxOneNonNull(null, null);
}
@Test
void shouldNotFailValidationIfExactlyOneParameterIsNonNull() {
Validate.validate("var1, var2").atMaxOneNonNull("val1", null);
Validate.validate("var1, var2").atMaxOneNonNull(null, "val2");
}
}

View File

@ -120,7 +120,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var count = rbacBookingItemRepo.count(); final var count = rbacBookingItemRepo.count();
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0); final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("First").get(0);
final var givenProject = realProjectRepo.findAllByDebitorUuid(givenDebitor.getUuid()).get(0); final var givenProject = realProjectRepo.findAllByDebitorUuid(givenDebitor.getUuid()).get(0);
// when // when
@ -151,7 +151,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// when // when
attempt(em, () -> { attempt(em, () -> {
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0); final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("First").get(0);
final var givenProject = realProjectRepo.findAllByDebitorUuid(givenDebitor.getUuid()).get(0); final var givenProject = realProjectRepo.findAllByDebitorUuid(givenDebitor.getUuid()).get(0);
final var newBookingItem = HsBookingItemRbacEntity.builder() final var newBookingItem = HsBookingItemRbacEntity.builder()
.project(givenProject) .project(givenProject)

View File

@ -79,7 +79,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
void globalAdmin_canFindCoopAssetsTransactionsByMemberNumber() { void globalAdmin_canFindCoopAssetsTransactionsByMemberNumber() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -202,7 +202,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
void globalAdmin_canFindCoopAssetsTransactionsByMembershipUuidAndDateRange() { void globalAdmin_canFindCoopAssetsTransactionsByMembershipUuidAndDateRange() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -235,7 +235,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
void globalAdmin_canPostNewCoopAssetTransaction() { void globalAdmin_canPostNewCoopAssetTransaction() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
.given() .given()
@ -280,7 +280,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
void globalAdmin_canAddCoopAssetsReversalTransaction() { void globalAdmin_canAddCoopAssetsReversalTransaction() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
final var givenTransaction = jpaAttempt.transacted(() -> { final var givenTransaction = jpaAttempt.transacted(() -> {
// TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...) // TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...)
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
@ -348,7 +348,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
void globalAdmin_canNotCancelMoreAssetsThanCurrentlySubscribed() { void globalAdmin_canNotCancelMoreAssetsThanCurrentlySubscribed() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()

View File

@ -915,7 +915,7 @@ class HsOfficeCoopAssetsTransactionControllerRestTest {
AVAILABLE_MEMBER_ENTITY); AVAILABLE_MEMBER_ENTITY);
final var availableMemberNumber = Integer.valueOf(AVAILABLE_TARGET_MEMBER_NUMBER.substring("M-".length())); final var availableMemberNumber = Integer.valueOf(AVAILABLE_TARGET_MEMBER_NUMBER.substring("M-".length()));
when(membershipRepo.findMembershipByMemberNumber(eq(availableMemberNumber))).thenReturn(AVAILABLE_MEMBER_ENTITY); when(membershipRepo.findMembershipByMemberNumber(eq(availableMemberNumber))).thenReturn(Optional.of(AVAILABLE_MEMBER_ENTITY));
when(membershipRepo.findByUuid(eq(ORIGIN_MEMBERSHIP_UUID))).thenReturn(Optional.of(ORIGIN_TARGET_MEMBER_ENTITY)); when(membershipRepo.findByUuid(eq(ORIGIN_MEMBERSHIP_UUID))).thenReturn(Optional.of(ORIGIN_TARGET_MEMBER_ENTITY));
when(membershipRepo.findByUuid(eq(AVAILABLE_TARGET_MEMBERSHIP_UUID))).thenReturn(Optional.of(AVAILABLE_MEMBER_ENTITY)); when(membershipRepo.findByUuid(eq(AVAILABLE_TARGET_MEMBERSHIP_UUID))).thenReturn(Optional.of(AVAILABLE_MEMBER_ENTITY));

View File

@ -62,7 +62,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var count = coopAssetsTransactionRepo.count(); final var count = coopAssetsTransactionRepo.count();
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).load(); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().load();
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
@ -94,7 +94,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// when // when
attempt(em, () -> { attempt(em, () -> {
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder() final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder()
.membership(givenMembership) .membership(givenMembership)
.transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT) .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT)
@ -166,7 +166,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
public void globalAdmin_canViewCoopAssetsTransactions_filteredByMembershipUuid() { public void globalAdmin_canViewCoopAssetsTransactions_filteredByMembershipUuid() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow();
// when // when
final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange(
@ -189,7 +189,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
public void globalAdmin_canViewCoopAssetsTransactions_filteredByMembershipUuidAndValueDateRange() { public void globalAdmin_canViewCoopAssetsTransactions_filteredByMembershipUuidAndValueDateRange() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow();
// when // when
final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange(

View File

@ -87,7 +87,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
void globalAdmin_canFindCoopSharesTransactionsByMemberNumber() { void globalAdmin_canFindCoopSharesTransactionsByMemberNumber() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow();
RestAssured // @formatter:off RestAssured // @formatter:off
.given().header("current-subject", "superuser-alex@hostsharing.net").port(port).when().get("http://localhost/api/hs/office/coopsharestransactions?membershipUuid=" + givenMembership.getUuid()).then().log().all().assertThat().statusCode(200).contentType("application/json").body("", lenientlyEquals(""" .given().header("current-subject", "superuser-alex@hostsharing.net").port(port).when().get("http://localhost/api/hs/office/coopsharestransactions?membershipUuid=" + givenMembership.getUuid()).then().log().all().assertThat().statusCode(200).contentType("application/json").body("", lenientlyEquals("""
@ -142,7 +142,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
void globalAdmin_canFindCoopSharesTransactionsByMembershipUuidAndDateRange() { void globalAdmin_canFindCoopSharesTransactionsByMembershipUuidAndDateRange() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow();
RestAssured // @formatter:off RestAssured // @formatter:off
.given().header("current-subject", "superuser-alex@hostsharing.net").port(port).when() .given().header("current-subject", "superuser-alex@hostsharing.net").port(port).when()
@ -167,7 +167,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
void globalAdmin_canAddCoopSharesTransaction() { void globalAdmin_canAddCoopSharesTransaction() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
.given().header("current-subject", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body(""" .given().header("current-subject", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body("""
@ -198,7 +198,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
void globalAdmin_canAddCoopSharesReversalTransaction() { void globalAdmin_canAddCoopSharesReversalTransaction() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
final var givenTransaction = jpaAttempt.transacted(() -> { final var givenTransaction = jpaAttempt.transacted(() -> {
// TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...) // TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...)
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
@ -266,7 +266,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
void globalAdmin_canNotCancelMoreSharesThanCurrentlySubscribed() { void globalAdmin_canNotCancelMoreSharesThanCurrentlySubscribed() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
RestAssured // @formatter:off RestAssured // @formatter:off
.given().header("current-subject", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body(""" .given().header("current-subject", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body("""

View File

@ -61,7 +61,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var count = coopSharesTransactionRepo.count(); final var count = coopSharesTransactionRepo.count();
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).load(); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().load();
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
@ -93,7 +93,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// when // when
attempt(em, () -> { attempt(em, () -> {
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow();
final var newCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder() final var newCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder()
.membership(givenMembership) .membership(givenMembership)
.transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION) .transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION)
@ -159,7 +159,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuid() { public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuid() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow();
// when // when
final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(
@ -180,7 +180,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuidAndValueDateRange() { public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuidAndValueDateRange() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow();
// when // when
final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(

View File

@ -31,7 +31,10 @@ import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.
import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid;
import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
@SpringBootTest( @SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
@ -74,6 +77,69 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
@PersistenceContext @PersistenceContext
EntityManager em; EntityManager em;
@Nested
class GetSingleDebitor {
@Test
void globalAdmin_withoutAssumedRoles_canGetDebitorByDebitorUuid() {
final var givenDebitor = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net");
return debitorRepo.findDebitorByDebitorNumber(1000212).orElseThrow();
}).assertSuccessful().returnedValue();
RestAssured // @formatter:off
.given()
.header("current-subject", "superuser-alex@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/hs/office/debitors/" + givenDebitor.getUuid())
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", lenientlyEquals("""
{
"debitorNumber": "D-1000212",
"partner": { "partnerNumber": "P-10002" },
"debitorRel": {
"contact": { "caption": "second contact" }
},
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true
}
"""));
// @formatter:on
}
@Test
void globalAdmin_withoutAssumedRoles_canGetDebitorByDebitorNumber() {
RestAssured // @formatter:off
.given()
.header("current-subject", "superuser-alex@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/hs/office/debitors/D-1000212")
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", lenientlyEquals("""
{
"debitorNumber": "D-1000212",
"partner": { "partnerNumber": "P-10002" },
"debitorRel": {
"contact": { "caption": "second contact" }
},
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true
}
"""));
// @formatter:on
}
}
@Nested @Nested
class GetListOfDebitors { class GetListOfDebitors {
@ -233,32 +299,28 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
} }
@Test @Test
void globalAdmin_withoutAssumedRoles_canFindDebitorDebitorByDebitorNumber() { void globalAdmin_withoutAssumedRoles_canFindDebitorsByPartnerNumber() {
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-subject", "superuser-alex@hostsharing.net") .header("current-subject", "superuser-alex@hostsharing.net")
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/office/debitors?debitorNumber=D-1000212") .get("http://localhost/api/hs/office/debitors?partnerNumber=P-10002")
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"debitorNumber": "D-1000212", "debitorNumber": "D-1000212",
"partner": { "partnerNumber": "P-10002" }, "partner": {
"debitorRel": { "partnerNumber": "P-10002"
"contact": { "caption": "second contact" } }
}, }
"vatId": null, ]
"vatCountryCode": null,
"vatBusiness": true
}
]
""")); """));
// @formatter:on // @formatter:on
} }
} }
@ -444,7 +506,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
@Test @Test
void globalAdmin_withoutAssumedRole_canGetArbitraryDebitor() { void globalAdmin_withoutAssumedRole_canGetArbitraryDebitor() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitorUuid = debitorRepo.findDebitorByOptionalNameLike("First").get(0).getUuid(); final var givenDebitorUuid = debitorRepo.findDebitorsByOptionalNameLike("First").get(0).getUuid();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -509,7 +571,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
@Test @Test
void normalUser_canNotGetUnrelatedDebitor() { void normalUser_canNotGetUnrelatedDebitor() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitorUuid = debitorRepo.findDebitorByOptionalNameLike("First").get(0).getUuid(); final var givenDebitorUuid = debitorRepo.findDebitorsByOptionalNameLike("First").get(0).getUuid();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -524,7 +586,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
@Test @Test
void contactAdminUser_canGetRelatedDebitorExceptRefundBankAccount() { void contactAdminUser_canGetRelatedDebitorExceptRefundBankAccount() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitorUuid = debitorRepo.findDebitorByOptionalNameLike("first contact").get(0).getUuid(); final var givenDebitorUuid = debitorRepo.findDebitorsByOptionalNameLike("first contact").get(0).getUuid();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()

View File

@ -50,7 +50,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
HsOfficePartnerRepository partnerRepo; HsOfficePartnerRepository partnerRepo;
@Autowired @Autowired
HsOfficeContactRealRepository contactrealRepo; HsOfficeContactRealRepository contactRealRepo;
@Autowired @Autowired
HsOfficePersonRealRepository personRepo; HsOfficePersonRealRepository personRepo;
@ -83,9 +83,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var count = debitorRepo.count(); final var count = debitorRepo.count();
final var givenPartner = partnerRepo.findPartnerByPartnerNumber(10001); final var givenPartner = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow();
final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH"));
final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike("first contact")); final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("first contact"));
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
@ -119,7 +119,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH"));
final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike("first contact")); final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("first contact"));
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
@ -158,7 +158,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
attempt(em, () -> { attempt(em, () -> {
final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH"));
final var givenDebitorPerson = one(personRepo.findPersonByOptionalNameLike("Fourth eG")); final var givenDebitorPerson = one(personRepo.findPersonByOptionalNameLike("Fourth eG"));
final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike("fourth contact")); final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("fourth contact"));
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix("22") .debitorNumberSuffix("22")
.debitorRel(HsOfficeRelationRealEntity.builder() .debitorRel(HsOfficeRelationRealEntity.builder()
@ -234,7 +234,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
// when // when
final var result = debitorRepo.findDebitorByOptionalNameLike(null); final var result = debitorRepo.findDebitorsByOptionalNameLike(null);
// then // then
allTheseDebitorsAreReturned( allTheseDebitorsAreReturned(
@ -256,7 +256,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
context("superuser-alex@hostsharing.net", assumedRole); context("superuser-alex@hostsharing.net", assumedRole);
// when: // when:
final var result = debitorRepo.findDebitorByOptionalNameLike(""); final var result = debitorRepo.findDebitorsByOptionalNameLike("");
// then: // then:
exactlyTheseDebitorsAreReturned(result, exactlyTheseDebitorsAreReturned(result,
@ -270,7 +270,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
context("selfregistered-test-user@hostsharing.org"); context("selfregistered-test-user@hostsharing.org");
// when: // when:
final var result = debitorRepo.findDebitorByOptionalNameLike(null); final var result = debitorRepo.findDebitorsByOptionalNameLike(null);
// then: // then:
assertThat(result).isEmpty(); assertThat(result).isEmpty();
@ -278,7 +278,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
} }
@Nested @Nested
class FindByDebitorNumberLike { class FindByDebitorNumber {
@Test @Test
public void globalAdmin_canViewAllDebitors() { public void globalAdmin_canViewAllDebitors() {
@ -288,6 +288,23 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// when // when
final var result = debitorRepo.findDebitorByDebitorNumber(1000313); final var result = debitorRepo.findDebitorByDebitorNumber(1000313);
// then
assertThat(result).map(Object::toString).contains(
"debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)");
}
}
@Nested
class FindByPartnerNumber {
@Test
public void globalAdmin_canViewAllDebitors() {
// given
context("superuser-alex@hostsharing.net");
// when
final var result = debitorRepo.findDebitorsByPartnerNumber(10003);
// then // then
exactlyTheseDebitorsAreReturned(result, exactlyTheseDebitorsAreReturned(result,
"debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)"); "debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)");
@ -303,7 +320,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
// when // when
final var result = debitorRepo.findDebitorByOptionalNameLike("third contact"); final var result = debitorRepo.findDebitorsByOptionalNameLike("third contact");
// then // then
exactlyTheseDebitorsAreReturned(result, "debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)"); exactlyTheseDebitorsAreReturned(result, "debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)");
@ -324,7 +341,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
"hs_office.relation#FourtheG-with-DEBITOR-FourtheG:ADMIN", true); "hs_office.relation#FourtheG-with-DEBITOR-FourtheG:ADMIN", true);
final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First")); final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First"));
final var givenNewBillingPerson = one(personRepo.findPersonByOptionalNameLike("Firby")); final var givenNewBillingPerson = one(personRepo.findPersonByOptionalNameLike("Firby"));
final var givenNewContact = one(contactrealRepo.findContactByOptionalCaptionLike("sixth contact")); final var givenNewContact = one(contactRealRepo.findContactByOptionalCaptionLike("sixth contact"));
final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first")); final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first"));
final String givenNewVatId = "NEW-VAT-ID"; final String givenNewVatId = "NEW-VAT-ID";
final String givenNewVatCountryCode = "NC"; final String givenNewVatCountryCode = "NC";
@ -613,7 +630,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenPartner = one(partnerRepo.findPartnerByOptionalNameLike(partnerName)); final var givenPartner = one(partnerRepo.findPartnerByOptionalNameLike(partnerName));
final var givenPartnerPerson = givenPartner.getPartnerRel().getHolder(); final var givenPartnerPerson = givenPartner.getPartnerRel().getHolder();
final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike(contactCaption)); final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike(contactCaption));
final var givenBankAccount = final var givenBankAccount =
bankAccountHolder != null ? one(bankAccountRepo.findByOptionalHolderLike(bankAccountHolder)) : null; bankAccountHolder != null ? one(bankAccountRepo.findByOptionalHolderLike(bankAccountHolder)) : null;
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()

View File

@ -112,16 +112,16 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
void globalAdmin_canViewMembershipsByPartnerUuid() { void globalAdmin_canViewMembershipsByPartnerUuid() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var partner = partnerRepo.findPartnerByPartnerNumber(10001); final var partner = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-subject", "superuser-alex@hostsharing.net") .header("current-subject", "superuser-alex@hostsharing.net")
.port(port) .port(port)
.when() .when()
.queryParam("partnerUuid", partner.getUuid() ) .queryParam("partnerUuid", partner.getUuid() )
.get("http://localhost/api/hs/office/memberships") .get("http://localhost/api/hs/office/memberships")
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
@ -140,30 +140,30 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
} }
@Test @Test
void globalAdmin_canViewMembershipsByMemberNumber() { void globalAdmin_canViewMembershipsByPartnerNumber() {
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-subject", "superuser-alex@hostsharing.net") .header("current-subject", "superuser-alex@hostsharing.net")
.port(port) .port(port)
.when() .when()
.queryParam("memberNumber", "M-1000202" ) .queryParam("partnerNumber", "P-10002" )
.get("http://localhost/api/hs/office/memberships") .get("http://localhost/api/hs/office/memberships")
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"partner": { "partnerNumber": "P-10002" }, "partner": { "partnerNumber": "P-10002" },
"memberNumber": "M-1000202", "memberNumber": "M-1000202",
"memberNumberSuffix": "02", "memberNumberSuffix": "02",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": null,
"status": "ACTIVE" "status": "ACTIVE"
} }
] ]
""")); """));
// @formatter:on // @formatter:on
} }
} }
@ -220,7 +220,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
@Test @Test
void globalAdmin_canGetArbitraryMembership() { void globalAdmin_canGetArbitraryMembership() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).getUuid(); final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().getUuid();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -246,7 +246,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
@Test @Test
void normalUser_canNotGetUnrelatedMembership() { void normalUser_canNotGetUnrelatedMembership() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).getUuid(); final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().getUuid();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -261,7 +261,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
@Test @Test
void partnerRelAgent_canGetRelatedMembership() { void partnerRelAgent_canGetRelatedMembership() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000303).getUuid(); final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000303).orElseThrow().getUuid();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()

View File

@ -19,11 +19,16 @@ import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static io.hypersistence.utils.hibernate.type.range.Range.localDateRange;
import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -33,6 +38,34 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@ActiveProfiles("test") @ActiveProfiles("test")
public class HsOfficeMembershipControllerRestTest { public class HsOfficeMembershipControllerRestTest {
private static final HsOfficePartnerEntity PARTNER_12345 = HsOfficePartnerEntity.builder()
.partnerNumber(12345)
.build();
public static final HsOfficeMembershipEntity MEMBERSHIP_1234501 = HsOfficeMembershipEntity.builder()
.partner(PARTNER_12345)
.memberNumberSuffix("01")
.validity(localDateRange("[2013-10-01,]"))
.status(HsOfficeMembershipStatus.ACTIVE)
.build();
public static final HsOfficeMembershipEntity MEMBERSHIP_1234500 = HsOfficeMembershipEntity.builder()
.partner(PARTNER_12345)
.memberNumberSuffix("00")
.validity(localDateRange("[2011-04-01,2016-12-31]"))
.status(HsOfficeMembershipStatus.CANCELLED)
.build();
public static final String MEMBERSHIP_1234501_JSON = """
{
"partner": {
"partnerNumber":"P-12345"
},
"memberNumber": "M-1234500",
"memberNumberSuffix": "00",
"validFrom": "2011-04-01",
"validTo": "2016-12-30",
"status":"CANCELLED"
}
""";
@Autowired @Autowired
MockMvc mockMvc; MockMvc mockMvc;
@ -52,11 +85,11 @@ public class HsOfficeMembershipControllerRestTest {
class GetListOfMemberships { class GetListOfMemberships {
@Test @Test
void findMembershipByNonExistingMemberNumberReturnsEmptyList() throws Exception { void findMembershipByNonExistingPartnerNumberReturnsEmptyList() throws Exception {
// when // when
mockMvc.perform(MockMvcRequestBuilders mockMvc.perform(MockMvcRequestBuilders
.get("/api/hs/office/memberships?memberNumber=M-1234501") .get("/api/hs/office/memberships?partnerNumber=P-12345")
.header("current-subject", "superuser-alex@hostsharing.net") .header("current-subject", "superuser-alex@hostsharing.net")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(""" .content("""
@ -73,6 +106,116 @@ public class HsOfficeMembershipControllerRestTest {
.andExpect(status().is2xxSuccessful()) .andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$", hasSize(0))); .andExpect(jsonPath("$", hasSize(0)));
} }
@Test
void findMembershipByExistingPartnerNumberReturnsAllRelatedMemberships() throws Exception {
// given
when(membershipRepo.findMembershipsByPartnerNumber(12345))
.thenReturn(List.of(
MEMBERSHIP_1234500,
MEMBERSHIP_1234501
));
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/hs/office/memberships?partnerNumber=P-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(2)));
}
}
@Nested
class GetSingleMembership {
@Test
void byUuid() throws Exception {
// given
final var givenUuid = UUID.randomUUID();
when(membershipRepo.findByUuid(givenUuid)).thenReturn(
Optional.of(MEMBERSHIP_1234500)
);
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/hs/office/memberships/" + givenUuid)
.header("current-subject", "superuser-alex@hostsharing.net")
.accept(MediaType.APPLICATION_JSON))
// then
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$", lenientlyEquals(MEMBERSHIP_1234501_JSON)));
}
@Test
void byUnavailableUuid() throws Exception {
// given
when(membershipRepo.findByUuid(any(UUID.class))).thenReturn(
Optional.empty()
);
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/hs/office/memberships/" + UUID.randomUUID())
.header("current-subject", "superuser-alex@hostsharing.net")
.accept(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isNotFound());
}
@Test
void byMemberNumber() throws Exception {
// given
when(membershipRepo.findMembershipByMemberNumber(1234501)).thenReturn(
Optional.of(MEMBERSHIP_1234500)
);
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/hs/office/memberships/M-1234501")
.header("current-subject", "superuser-alex@hostsharing.net")
.accept(MediaType.APPLICATION_JSON))
// then
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$", lenientlyEquals(MEMBERSHIP_1234501_JSON)));
}
@Test
void byUnavailableMemberNumber() throws Exception {
// given
when(membershipRepo.findMembershipByMemberNumber(any(Integer.class))).thenReturn(
Optional.empty()
);
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/hs/office/memberships/M-0000000")
.header("current-subject", "superuser-alex@hostsharing.net")
.accept(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isNotFound());
}
} }
@Nested @Nested
@ -163,10 +306,10 @@ public class HsOfficeMembershipControllerRestTest {
public enum InvalidMemberSuffixVariants { public enum InvalidMemberSuffixVariants {
MISSING("", "[memberNumberSuffix must not be null but is \"null\"]"), MISSING("", "[memberNumberSuffix must not be null but is \"null\"]"),
TOO_SMALL("\"memberNumberSuffix\": \"9\",", "memberNumberSuffix size must be between 2 and 2 but is \"9\""), TOO_SMALL("\"memberNumberSuffix\": \"9\",", "memberNumberSuffix must match \"[0-9]{2}\" but is \"9\""),
TOO_LARGE("\"memberNumberSuffix\": \"100\",", "memberNumberSuffix size must be between 2 and 2 but is \"100\""), TOO_LARGE("\"memberNumberSuffix\": \"100\",", "memberNumberSuffix must match \"[0-9]{2}\" but is \"100\""),
NOT_NUMERIC("\"memberNumberSuffix\": \"AA\",", "memberNumberSuffix must match \"[0-9]+\" but is \"AA\""), NOT_NUMERIC("\"memberNumberSuffix\": \"AA\",", "memberNumberSuffix must match \"[0-9]{2}\" but is \"AA\""),
EMPTY("\"memberNumberSuffix\": \"\",", "memberNumberSuffix size must be between 2 and 2 but is \"\""); EMPTY("\"memberNumberSuffix\": \"\",", "memberNumberSuffix must match \"[0-9]{2}\" but is \"\"");
private final String memberNumberSuffixEntry; private final String memberNumberSuffixEntry;
private final String expectedErrorMessage; private final String expectedErrorMessage;

View File

@ -156,7 +156,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
// when // when
final var result = membershipRepo.findMembershipsByOptionalPartnerUuid(null); final var result = membershipRepo.findAll();
// then // then
exactlyTheseMembershipsAreReturned( exactlyTheseMembershipsAreReturned(
@ -173,7 +173,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0); final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
// when // when
final var result = membershipRepo.findMembershipsByOptionalPartnerUuid(givenPartner.getUuid()); final var result = membershipRepo.findMembershipsByPartnerUuid(givenPartner.getUuid());
// then // then
exactlyTheseMembershipsAreReturned(result, exactlyTheseMembershipsAreReturned(result,
@ -186,7 +186,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
// when // when
final var result = membershipRepo.findMembershipByMemberNumber(1000202); final var result = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow();
// then // then
assertThat(result) assertThat(result)
@ -194,6 +194,34 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
.extracting(Object::toString) .extracting(Object::toString)
.isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); .isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)");
} }
@Test
public void globalAdmin_withoutAssumedRole_canFindAllMembershipsByPartnerNumberAndSuffix() {
// given
context("superuser-alex@hostsharing.net");
// when
final var result = membershipRepo.findMembershipByPartnerNumberAndSuffix(10002, "02").orElseThrow();
// then
assertThat(result)
.isNotNull()
.extracting(Object::toString)
.isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)");
}
@Test
public void globalAdmin_withoutAssumedRole_canFindAllMembershipsByPartnerNumber() {
// given
context("superuser-alex@hostsharing.net");
// when
final var result = membershipRepo.findMembershipsByPartnerNumber(10002);
// then
exactlyTheseMembershipsAreReturned(result,
"Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)");
}
} }
@Nested @Nested
@ -339,7 +367,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
select currentTask, targetTable, targetOp, targetdelta->>'membernumbersuffix' select currentTask, targetTable, targetOp, targetdelta->>'membernumbersuffix'
from base.tx_journal_v from base.tx_journal_v
where targettable = 'hs_office.membership'; where targettable = 'hs_office.membership';
"""); """);
// when // when
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList(); @SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();

View File

@ -168,6 +168,45 @@ class HsOfficePartnerControllerRestTest {
} }
} }
@Nested
class GetSinglePartnerByPartnerNumber {
@Test
void respondWithPartner_ifPartnerNumberIsAvailable() throws Exception {
// given
when(partnerRepo.findPartnerByPartnerNumber(12345)).thenReturn(Optional.of(HsOfficePartnerEntity.builder()
.partnerNumber(12345)
.build()));
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/hs/office/partners/P-12345")
.header("current-subject", "superuser-alex@hostsharing.net")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("partnerNumber", is("P-12345")));
}
@Test
void respondNotFound_ifPartnerNumberIsNotAvailable() throws Exception {
// given
when(partnerRepo.findPartnerByPartnerNumber(12345)).thenReturn(Optional.empty());
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/hs/office/partners/P-12345")
.header("current-subject", "superuser-alex@hostsharing.net")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isNotFound());
}
}
@Nested @Nested
class DeletePartner { class DeletePartner {

View File

@ -243,7 +243,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
// when // when
final var result = partnerRepo.findPartnerByPartnerNumber(10001); final var result = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow();
// then // then
assertThat(result) assertThat(result)

View File

@ -17,9 +17,9 @@ public class CreateSepaMandateForDebitor extends UseCase<CreateSepaMandateForDeb
protected HttpResponse run() { protected HttpResponse run() {
obtain("Debitor: Test AG - main debitor", () -> obtain("Debitor: Test AG - main debitor", () ->
httpGet("/api/hs/office/debitors?debitorNumber=&{debitorNumber}") httpGet("/api/hs/office/debitors/%{debitorNumber}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid") response -> response.getFromBody("uuid")
); );
obtain("BankAccount: Test AG - debit bank account", () -> obtain("BankAccount: Test AG - debit bank account", () ->

View File

@ -18,9 +18,8 @@ public class CancelMembership extends UseCase<CancelMembership> {
protected HttpResponse run() { protected HttpResponse run() {
obtain("Membership: %{memberNumber}", () -> obtain("Membership: %{memberNumber}", () ->
httpGet("/api/hs/office/memberships?memberNumber=%{memberNumber}") httpGet("/api/hs/office/memberships/%{memberNumber}"),
.expectArrayElements(1), response -> response.getFromBody("uuid")
response -> response.expectArrayElements(1).getFromBody("[0].uuid")
); );
return withTitle("Patch the New Status Into the Membership", () -> return withTitle("Patch the New Status Into the Membership", () ->

View File

@ -18,9 +18,9 @@ public abstract class CreateCoopAssetsTransaction extends UseCase<CreateCoopAsse
protected HttpResponse run() { protected HttpResponse run() {
obtain("#{Find }membershipUuid", () -> obtain("#{Find }membershipUuid", () ->
httpGet("/api/hs/office/memberships?memberNumber=&{memberNumber}") httpGet("/api/hs/office/memberships/%{memberNumber}")
.expecting(OK).expecting(JSON).expectArrayElements(1), .expecting(OK).expecting(JSON),
response -> response.getFromBody("$[0].uuid") response -> response.getFromBody("uuid")
); );
return withTitle("Create the Coop-Assets-%{transactionType} Transaction", () -> return withTitle("Create the Coop-Assets-%{transactionType} Transaction", () ->

View File

@ -18,9 +18,9 @@ public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopShar
protected HttpResponse run() { protected HttpResponse run() {
obtain("#{Find }membershipUuid", () -> obtain("#{Find }membershipUuid", () ->
httpGet("/api/hs/office/memberships?memberNumber=&{memberNumber}") httpGet("/api/hs/office/memberships/%{memberNumber}")
.expecting(OK).expecting(JSON).expectArrayElements(1), .expecting(OK).expecting(JSON),
response -> response.getFromBody("$[0].uuid") response -> response.getFromBody("uuid")
); );
return withTitle("Create the Coop-Shares-%{transactionType} Transaction", () -> return withTitle("Create the Coop-Shares-%{transactionType} Transaction", () ->

View File

@ -138,7 +138,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
void globalAdmin_canPostNewSepaMandate() { void globalAdmin_canPostNewSepaMandate() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("Third").get(0); final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("Third").get(0);
final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc("DE02200505501015871393").get(0); final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc("DE02200505501015871393").get(0);
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
@ -180,7 +180,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
void globalAdmin_canNotPostNewSepaMandateWhenDebitorUuidIsMissing() { void globalAdmin_canNotPostNewSepaMandateWhenDebitorUuidIsMissing() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("Third").get(0); final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("Third").get(0);
final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc("DE02200505501015871393").get(0); final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc("DE02200505501015871393").get(0);
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
@ -205,7 +205,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
void globalAdmin_canNotPostNewSepaMandate_ifBankAccountDoesNotExist() { void globalAdmin_canNotPostNewSepaMandate_ifBankAccountDoesNotExist() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("Third").get(0); final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("Third").get(0);
final var givenBankAccountUuid = UUID.fromString("00000000-0000-0000-0000-000000000000"); final var givenBankAccountUuid = UUID.fromString("00000000-0000-0000-0000-000000000000");
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
@ -524,7 +524,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
private HsOfficeSepaMandateEntity givenSomeTemporarySepaMandateForDebitorNumber(final int debitorNumber) { private HsOfficeSepaMandateEntity givenSomeTemporarySepaMandateForDebitorNumber(final int debitorNumber) {
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = debitorRepo.findDebitorByDebitorNumber(debitorNumber).get(0); final var givenDebitor = debitorRepo.findDebitorByDebitorNumber(debitorNumber).orElseThrow();
final var bankAccountHolder = ofNullable(givenDebitor.getPartner().getPartnerRel().getHolder().getTradeName()) final var bankAccountHolder = ofNullable(givenDebitor.getPartner().getPartnerRel().getHolder().getTradeName())
.orElse(givenDebitor.getPartner().getPartnerRel().getHolder().getFamilyName()); .orElse(givenDebitor.getPartner().getPartnerRel().getHolder().getFamilyName());
final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike(bankAccountHolder).get(0); final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike(bankAccountHolder).get(0);

View File

@ -66,7 +66,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTestWithC
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var count = sepaMandateRepo.count(); final var count = sepaMandateRepo.count();
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0); final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("First").get(0);
final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike("Paul Winkler").get(0); final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike("Paul Winkler").get(0);
// when // when
@ -100,7 +100,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTestWithC
// when // when
attempt(em, () -> { attempt(em, () -> {
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0); final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("First").get(0);
final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike("Paul Winkler").get(0); final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike("Paul Winkler").get(0);
final var newSepaMandate = HsOfficeSepaMandateEntity.builder() final var newSepaMandate = HsOfficeSepaMandateEntity.builder()
.debitor(givenDebitor) .debitor(givenDebitor)
@ -397,7 +397,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTestWithC
private HsOfficeSepaMandateEntity givenSomeTemporarySepaMandate(final String iban) { private HsOfficeSepaMandateEntity givenSomeTemporarySepaMandate(final String iban) {
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0); final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("First").get(0);
final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc(iban).get(0); final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc(iban).get(0);
final var newSepaMandate = HsOfficeSepaMandateEntity.builder() final var newSepaMandate = HsOfficeSepaMandateEntity.builder()
.debitor(givenDebitor) .debitor(givenDebitor)