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

Merged
9 changed files with 142 additions and 20 deletions
Showing only changes of commit ab2c5245e9 - Show all commits

View File

@ -57,11 +57,11 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
final String currentSubject,
final String assumedRoles,
final String name,
final String debitorNumber) {
final String partnerNumber) {
context.define(currentSubject, assumedRoles);
final var entities = debitorNumber != null
? debitorRepo.findDebitorByDebitorNumber(cropTag("D-", debitorNumber))
final var entities = partnerNumber != null
? debitorRepo.findDebitorByPartnerNumber(cropTag("P-", partnerNumber))
: debitorRepo.findDebitorByOptionalNameLike(name);
final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
@ -133,6 +133,23 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
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
@Transactional
@Timed("app.office.debitors.api.deleteDebitorByUuid")

View File

@ -1,6 +1,7 @@
package net.hostsharing.hsadminng.hs.office.debitor;
import io.micrometer.core.annotation.Timed;
import net.hostsharing.hsadminng.lambda.Reducer;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
@ -19,15 +20,20 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
ON partner.partnerRel.holder = debitor.debitorRel.anchor
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
WHERE partner.partnerNumber = :partnerNumber
AND debitor.debitorNumberSuffix = :debitorNumberSuffix
AND (:debitorNumberSuffix IS NULL OR debitor.debitorNumberSuffix = :debitorNumberSuffix)
""")
@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 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> findDebitorByPartnerNumber(int partnerNumber) {
final var result = findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(partnerNumber, null);
return result;
}

View File

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

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,15 @@ get:
schema:
type: string
description: Prefix of name properties from person or contact to filter the results.
- name: debitorNumber
- name: partnerNumber
in: query
required: false
schema:
type: string
minLength: 9
maxLength: 9
pattern: 'D-[0-9]{7}'
description: Debitor number of the requested debitor.
pattern: 'P-[0-9]{5}'
description: Partner number of the requested debitor.
responses:
"200":
description: OK

View File

@ -61,6 +61,9 @@ paths:
/api/hs/office/debitors:
$ref: "hs-office-debitors.yaml"
/api/hs/office/debitors/D-{debitorNumber}:
$ref: "hs-office-debitors-with-debitorNumber.yaml"
/api/hs/office/debitors/{debitorUUID}:
$ref: "hs-office-debitors-with-uuid.yaml"

View File

@ -233,7 +233,56 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
}
@Test
void globalAdmin_withoutAssumedRoles_canFindDebitorDebitorByDebitorNumber() {
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/P-10002")
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", lenientlyEquals("""
[
{
"debitorNumber": "D-1000211",
"partner": { "partnerNumber": "P-10002" },
"debitorRel": {
"contact": { "caption": "second contact" }
},
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true
},
{
"debitorNumber": "D-1000212",
"partner": { "partnerNumber": "P-10002" },
"debitorRel": {
"contact": { "caption": "second contact" }
},
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true
},
{
"debitorNumber": "D-1000213",
"partner": { "partnerNumber": "P-10002" },
"debitorRel": {
"contact": { "caption": "second contact" }
},
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true
}
]
"""));
// @formatter:on
}
@Test
void globalAdmin_withoutAssumedRoles_canFindDebitorsByPartnerNumber() {
RestAssured // @formatter:off
.given()

View File

@ -50,7 +50,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
HsOfficePartnerRepository partnerRepo;
@Autowired
HsOfficeContactRealRepository contactrealRepo;
HsOfficeContactRealRepository contactRealRepo;
@Autowired
HsOfficePersonRealRepository personRepo;
@ -85,7 +85,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
final var count = debitorRepo.count();
final var givenPartner = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow();
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
final var result = attempt(em, () -> {
@ -119,7 +119,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// given
context("superuser-alex@hostsharing.net");
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
final var result = attempt(em, () -> {
@ -158,7 +158,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
attempt(em, () -> {
final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH"));
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()
.debitorNumberSuffix("22")
.debitorRel(HsOfficeRelationRealEntity.builder()
@ -278,7 +278,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
}
@Nested
class FindByDebitorNumberLike {
class FindByDebitorNumber {
@Test
public void globalAdmin_canViewAllDebitors() {
@ -288,6 +288,23 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// when
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.findDebitorByPartnerNumber(10003);
// then
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);
final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First"));
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 String givenNewVatId = "NEW-VAT-ID";
final String givenNewVatCountryCode = "NC";
@ -613,7 +630,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
context("superuser-alex@hostsharing.net");
final var givenPartner = one(partnerRepo.findPartnerByOptionalNameLike(partnerName));
final var givenPartnerPerson = givenPartner.getPartnerRel().getHolder();
final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike(contactCaption));
final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike(contactCaption));
final var givenBankAccount =
bankAccountHolder != null ? one(bankAccountRepo.findByOptionalHolderLike(bankAccountHolder)) : null;
final var newDebitor = HsOfficeDebitorEntity.builder()

View File

@ -524,7 +524,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
private HsOfficeSepaMandateEntity givenSomeTemporarySepaMandateForDebitorNumber(final int debitorNumber) {
return jpaAttempt.transacted(() -> {
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())
.orElse(givenDebitor.getPartner().getPartnerRel().getHolder().getFamilyName());
final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike(bankAccountHolder).get(0);