From dc6da887a0fd1f299325f6397200b20780a66591 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 7 Dec 2024 12:01:37 +0100 Subject: [PATCH] getListOfDebitors with partnerNumber and getSingleDebitorByDebitorNumber --- .../debitor/HsOfficeDebitorController.java | 23 ++++++-- .../debitor/HsOfficeDebitorRepository.java | 14 +++-- .../hs-office/api-mappings.yaml | 1 + ...hs-office-debitors-with-debitorNumber.yaml | 29 ++++++++++ .../hs-office/hs-office-debitors.yaml | 6 +-- .../api-definition/hs-office/hs-office.yaml | 3 ++ ...OfficeDebitorControllerAcceptanceTest.java | 53 ++++++++++++++++++- ...fficeDebitorRepositoryIntegrationTest.java | 31 ++++++++--- ...ceSepaMandateControllerAcceptanceTest.java | 2 +- 9 files changed, 142 insertions(+), 20 deletions(-) create mode 100644 src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java index 1792acdb..b1dd3172 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java @@ -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 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") diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java index a62d1c42..a48bfa2b 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java @@ -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 findDebitorByPartnerNumberAndDebitorNumberSuffix(int partnerNumber, String debitorNumberSuffix); + List findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(int partnerNumber, String debitorNumberSuffix); - default List findDebitorByDebitorNumber(int debitorNumber) { + default Optional 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 findDebitorByPartnerNumber(int partnerNumber) { + final var result = findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(partnerNumber, null); return result; } diff --git a/src/main/resources/api-definition/hs-office/api-mappings.yaml b/src/main/resources/api-definition/hs-office/api-mappings.yaml index 2403e1e4..3a13afd4 100644 --- a/src/main/resources/api-definition/hs-office/api-mappings.yaml +++ b/src/main/resources/api-definition/hs-office/api-mappings.yaml @@ -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}: diff --git a/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml b/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml new file mode 100644 index 00000000..9e34b758 --- /dev/null +++ b/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml @@ -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' diff --git a/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml b/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml index c825817f..74fab6dc 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml @@ -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 diff --git a/src/main/resources/api-definition/hs-office/hs-office.yaml b/src/main/resources/api-definition/hs-office/hs-office.yaml index 71aa708b..b7725285 100644 --- a/src/main/resources/api-definition/hs-office/hs-office.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office.yaml @@ -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" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java index d0b56745..4fb18e61 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java @@ -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() @@ -258,7 +307,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu } ] """)); - // @formatter:on + // @formatter:on } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java index fb0c0c94..f821d3b6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java @@ -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() diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java index 63cce53f..b7198aaa 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java @@ -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);