feature/prefixes-for-partner-member-debitor-in-api #122

Merged
hsh-michaelhoennig merged 10 commits from feature/prefixes-for-partner-member-debitor-in-api into master 2024-11-18 12:11:18 +01:00
13 changed files with 49 additions and 50 deletions
Showing only changes of commit f5153cc2a1 - Show all commits

View File

@ -22,6 +22,7 @@ import jakarta.persistence.PersistenceContext;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR; import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag; import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
@ -50,7 +51,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeDebitorResource>> listDebitors( public ResponseEntity<List<HsOfficeDebitorResource>> getListOfDebitors(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final String name, final String name,
@ -61,13 +62,13 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
? debitorRepo.findDebitorByDebitorNumber(cropTag("D-", debitorNumber)) ? debitorRepo.findDebitorByDebitorNumber(cropTag("D-", debitorNumber))
: debitorRepo.findDebitorByOptionalNameLike(name); : debitorRepo.findDebitorByOptionalNameLike(name);
final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class); final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources); return ResponseEntity.ok(resources);
} }
@Override @Override
@Transactional @Transactional
public ResponseEntity<HsOfficeDebitorResource> addDebitor( public ResponseEntity<HsOfficeDebitorResource> postNewDebitor(
String currentSubject, String currentSubject,
String assumedRoles, String assumedRoles,
HsOfficeDebitorInsertResource body) { HsOfficeDebitorInsertResource body) {
@ -108,13 +109,13 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
.path("/api/hs/office/debitors/{id}") .path("/api/hs/office/debitors/{id}")
.buildAndExpand(savedEntity.getUuid()) .buildAndExpand(savedEntity.getUuid())
.toUri(); .toUri();
final var mapped = mapper.map(savedEntity, HsOfficeDebitorResource.class); final var mapped = mapper.map(savedEntity, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.created(uri).body(mapped); return ResponseEntity.created(uri).body(mapped);
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<HsOfficeDebitorResource> getDebitorByUuid( public ResponseEntity<HsOfficeDebitorResource> getSingleDebitorByUuid(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final UUID debitorUuid) { final UUID debitorUuid) {
@ -125,7 +126,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
if (result.isEmpty()) { if (result.isEmpty()) {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }
return ResponseEntity.ok(mapper.map(result.get(), HsOfficeDebitorResource.class)); return ResponseEntity.ok(mapper.map(result.get(), HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER));
} }
@Override @Override
@ -160,7 +161,11 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
final var saved = debitorRepo.save(current); final var saved = debitorRepo.save(current);
Hibernate.initialize(saved); Hibernate.initialize(saved);
final var mapped = mapper.map(saved, HsOfficeDebitorResource.class); final var mapped = mapper.map(saved, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped); return ResponseEntity.ok(mapped);
} }
final BiConsumer<HsOfficeDebitorEntity, HsOfficeDebitorResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setDebitorNumber(entity.getTaggedDebitorNumber());
};
} }

View File

@ -142,19 +142,14 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
return this; return this;
} }
private String getDebitorNumberString() { public String getTaggedDebitorNumber() {
return ofNullable(partner) return ofNullable(partner)
.filter(partner -> debitorNumberSuffix != null) .filter(partner -> debitorNumberSuffix != null)
.map(HsOfficePartnerEntity::getPartnerNumber) .map(HsOfficePartnerEntity::getPartnerNumber)
.map(Object::toString) .map(partnerNumber -> DEBITOR_NUMBER_TAG + partnerNumber + debitorNumberSuffix)
.map(partnerNumber -> partnerNumber + debitorNumberSuffix)
.orElse(null); .orElse(null);
} }
public Integer getDebitorNumber() {
return ofNullable(getDebitorNumberString()).map(Integer::parseInt).orElse(null);
}
@Override @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);
@ -162,7 +157,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
@Override @Override
public String toShortString() { public String toShortString() {
return DEBITOR_NUMBER_TAG + getDebitorNumberString(); return getTaggedDebitorNumber();
} }
public static RbacView rbac() { public static RbacView rbac() {

View File

@ -1,10 +1,8 @@
package net.hostsharing.hsadminng.hs.office.debitor; package net.hostsharing.hsadminng.hs.office.debitor;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
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;
import jakarta.validation.constraints.NotNull;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;

View File

@ -20,7 +20,6 @@ import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag; import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
@RestController @RestController
public class HsOfficeMembershipController implements HsOfficeMembershipsApi { public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
@Autowired @Autowired
@ -34,16 +33,18 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeMembershipResource>> listMemberships( public ResponseEntity<List<HsOfficeMembershipResource>> getListOfMemberships(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final UUID partnerUuid, final UUID partnerUuid,
final String memberNumber) { final String memberNumber) {
context.define(currentSubject, assumedRoles); context.define(currentSubject, assumedRoles);
final var entities = ( memberNumber != null) final var entities = (memberNumber != null)
? ofNullable(membershipRepo.findMembershipByMemberNumber(cropTag("M-", memberNumber))).stream().toList() ? ofNullable(membershipRepo.findMembershipByMemberNumber(
: membershipRepo.findMembershipsByOptionalPartnerUuid(partnerUuid); cropTag(HsOfficeMembershipEntity.MEMBER_NUMBER_TAG, memberNumber))).stream()
.toList()
: membershipRepo.findMembershipsByOptionalPartnerUuid(partnerUuid);
final var resources = mapper.mapList(entities, HsOfficeMembershipResource.class, final var resources = mapper.mapList(entities, HsOfficeMembershipResource.class,
SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER);
@ -52,7 +53,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
@Override @Override
@Transactional @Transactional
public ResponseEntity<HsOfficeMembershipResource> addMembership( public ResponseEntity<HsOfficeMembershipResource> postNewMembership(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final HsOfficeMembershipInsertResource body) { final HsOfficeMembershipInsertResource body) {
@ -75,7 +76,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<HsOfficeMembershipResource> getMembershipByUuid( public ResponseEntity<HsOfficeMembershipResource> getSingleMembershipByUuid(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final UUID membershipUuid) { final UUID membershipUuid) {

View File

@ -131,7 +131,7 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
if (entity.getValidity().hasUpperBound()) { if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1)); resource.setValidTo(entity.getValidity().upper().minusDays(1));
} }
resource.getDebitor().setDebitorNumber("D-" + entity.getDebitor().getDebitorNumber()); resource.getDebitor().setDebitorNumber(entity.getDebitor().getTaggedDebitorNumber());
}; };
final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {

View File

@ -2,7 +2,7 @@ get:
tags: tags:
- hs-office-debitors - hs-office-debitors
description: 'Fetch a single debitor by its uuid, if visible for the current subject.' description: 'Fetch a single debitor by its uuid, if visible for the current subject.'
operationId: getDebitorByUuid operationId: getSingleDebitorByUuid
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -3,7 +3,7 @@ get:
description: Returns the list of (optionally filtered) debitors which are visible to the current subject or any of it's assumed roles. description: Returns the list of (optionally filtered) debitors which are visible to the current subject or any of it's assumed roles.
tags: tags:
- hs-office-debitors - hs-office-debitors
operationId: listDebitors operationId: getListOfDebitors
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
@ -40,7 +40,7 @@ post:
summary: Adds a new debitor. summary: Adds a new debitor.
tags: tags:
- hs-office-debitors - hs-office-debitors
operationId: addDebitor operationId: postNewDebitor
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -2,7 +2,7 @@ get:
tags: tags:
- hs-office-memberships - hs-office-memberships
description: 'Fetch a single membership by its uuid, if visible for the current subject.' description: 'Fetch a single membership by its uuid, if visible for the current subject.'
operationId: getMembershipByUuid operationId: getSingleMembershipByUuid
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -4,7 +4,7 @@ get:
The list can optionally be filtered by either the `partnerUuid` or the `memberNumber` - not both at the same time. The list can optionally be filtered by either the `partnerUuid` or the `memberNumber` - not both at the same time.
tags: tags:
- hs-office-memberships - hs-office-memberships
operationId: listMemberships operationId: getListOfMemberships
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
@ -42,7 +42,7 @@ post:
summary: Adds a new membership. summary: Adds a new membership.
tags: tags:
- hs-office-memberships - hs-office-memberships
operationId: addMembership operationId: postNewMembership
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -72,7 +72,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
EntityManager em; EntityManager em;
@Nested @Nested
class ListDebitors { class GetListOfDebitors {
@Test @Test
void globalAdmin_withoutAssumedRoles_canViewAllDebitors_ifNoCriteriaGiven() { void globalAdmin_withoutAssumedRoles_canViewAllDebitors_ifNoCriteriaGiven() {
@ -110,7 +110,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
"phoneNumbers": { "phone_office": "+49 123 1234567" } "phoneNumbers": { "phone_office": "+49 123 1234567" }
} }
}, },
"debitorNumber": 1000111, "debitorNumber": "D-1000111",
"debitorNumberSuffix": "11", "debitorNumberSuffix": "11",
"partner": { "partner": {
"partnerNumber": "P-10001", "partnerNumber": "P-10001",
@ -165,7 +165,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
"emailAddresses": { "main": "contact-admin@secondcontact.example.com" } "emailAddresses": { "main": "contact-admin@secondcontact.example.com" }
} }
}, },
"debitorNumber": 1000212, "debitorNumber": "D-1000212",
"debitorNumberSuffix": "12", "debitorNumberSuffix": "12",
"partner": { "partner": {
"partnerNumber": "P-10002", "partnerNumber": "P-10002",
@ -199,7 +199,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
"emailAddresses": { "main": "contact-admin@thirdcontact.example.com" } "emailAddresses": { "main": "contact-admin@thirdcontact.example.com" }
} }
}, },
"debitorNumber": 1000313, "debitorNumber": "D-1000313",
"debitorNumberSuffix": "13", "debitorNumberSuffix": "13",
"partner": { "partner": {
"partnerNumber": "P-10003", "partnerNumber": "P-10003",
@ -237,14 +237,14 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.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=1000212") .get("http://localhost/api/hs/office/debitors?debitorNumber=D-1000212")
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"debitorNumber": 1000212, "debitorNumber": "D-1000212",
"partner": { "partnerNumber": "P-10002" }, "partner": { "partnerNumber": "P-10002" },
"debitorRel": { "debitorRel": {
"contact": { "caption": "second contact" } "contact": { "caption": "second contact" }
@ -260,7 +260,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
} }
@Nested @Nested
class AddDebitor { class PostNewDebitor {
@Test @Test
void globalAdmin_withoutAssumedRole_canAddDebitorWithBankAccount() { void globalAdmin_withoutAssumedRole_canAddDebitorWithBankAccount() {
@ -436,7 +436,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
} }
@Nested @Nested
class GetDebitor { class GetSingleDebitorByUuid {
@Test @Test
void globalAdmin_withoutAssumedRole_canGetArbitraryDebitor() { void globalAdmin_withoutAssumedRole_canGetArbitraryDebitor() {
@ -534,7 +534,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"debitorNumber": 1000111, "debitorNumber": "D-1000111",
"partner": { "partnerNumber": "P-10001" }, "partner": { "partnerNumber": "P-10001" },
"debitorRel": { "contact": { "caption": "first contact" } }, "debitorRel": { "contact": { "caption": "first contact" } },
"refundBankAccount": null "refundBankAccount": null
@ -581,7 +581,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
"mark": null, "mark": null,
"contact": { "caption": "fourth contact" } "contact": { "caption": "fourth contact" }
}, },
"debitorNumber": 10004${debitorNumberSuffix}, "debitorNumber": "D-10004${debitorNumberSuffix}",
"debitorNumberSuffix": "${debitorNumberSuffix}", "debitorNumberSuffix": "${debitorNumberSuffix}",
"partner": { "partner": {
"partnerNumber": "P-10004", "partnerNumber": "P-10004",

View File

@ -65,9 +65,9 @@ class HsOfficeDebitorEntityUnitTest {
.build()) .build())
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getTaggedDebitorNumber();
assertThat(result).isEqualTo(1234567); assertThat(result).isEqualTo("D-1234567");
} }
@Test @Test
@ -78,7 +78,7 @@ class HsOfficeDebitorEntityUnitTest {
.partner(null) .partner(null)
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getTaggedDebitorNumber();
assertThat(result).isNull(); assertThat(result).isNull();
} }
@ -91,7 +91,7 @@ class HsOfficeDebitorEntityUnitTest {
.partner(HsOfficePartnerEntity.builder().build()) .partner(HsOfficePartnerEntity.builder().build())
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getTaggedDebitorNumber();
assertThat(result).isNull(); assertThat(result).isNull();
} }
@ -106,7 +106,7 @@ class HsOfficeDebitorEntityUnitTest {
.build()) .build())
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getTaggedDebitorNumber();
assertThat(result).isNull(); assertThat(result).isNull();
} }

View File

@ -144,7 +144,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.header("current-subject", "superuser-alex@hostsharing.net") .header("current-subject", "superuser-alex@hostsharing.net")
.port(port) .port(port)
.when() .when()
.queryParam("memberNumber", 1000202 ) .queryParam("memberNumber", "M-1000202" )
.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)

View File

@ -46,14 +46,14 @@ public class HsOfficeMembershipControllerRestTest {
EntityManagerWrapper em; EntityManagerWrapper em;
@Nested @Nested
class GetMemberships { class GetListOfMemberships {
@Test @Test
void findMembershipByNonExistingMemberNumberReturnsEmptyList() throws Exception { void findMembershipByNonExistingMemberNumberReturnsEmptyList() throws Exception {
// when // when
mockMvc.perform(MockMvcRequestBuilders mockMvc.perform(MockMvcRequestBuilders
.get("/api/hs/office/memberships?memberNumber=12345") .get("/api/hs/office/memberships?memberNumber=M-1234501")
.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,7 +73,7 @@ public class HsOfficeMembershipControllerRestTest {
} }
@Nested @Nested
class AddMembership { class PostNewMembership {
@Test @Test
void respondBadRequest_ifPartnerUuidIsMissing() throws Exception { void respondBadRequest_ifPartnerUuidIsMissing() throws Exception {