src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepository.java | ●●●●● patch | view | raw | blame | history | |
src/main/resources/api-definition/hs-office/hs-office-coopshares-schemas.yaml | ●●●●● patch | view | raw | blame | history | |
src/main/resources/api-definition/hs-office/hs-office-coopshares.yaml | ●●●●● patch | view | raw | blame | history | |
src/main/resources/api-definition/hs-office/hs-office.yaml | ●●●●● patch | view | raw | blame | history | |
src/main/resources/db/changelog/318-hs-office-coopshares-test-data.sql | ●●●●● patch | view | raw | blame | history | |
src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java | ●●●●● patch | view | raw | blame | history |
src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java
New file @@ -0,0 +1,96 @@ package net.hostsharing.hsadminng.hs.office.coopshares; import net.hostsharing.hsadminng.Mapper; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import javax.persistence.EntityManager; import javax.validation.Valid; import java.time.LocalDate; import java.util.List; import java.util.UUID; import java.util.function.BiConsumer; import static net.hostsharing.hsadminng.Mapper.map; @RestController public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopSharesApi { @Autowired private Context context; @Autowired private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; @Autowired private EntityManager em; @Override @Transactional(readOnly = true) public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> listCoopShares( final String currentUser, final String assumedRoles, final UUID membershipUuid, Integer memberNumber, // TODO: remove final @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate fromValueDate, final @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate toValueDate) { context.define(currentUser, assumedRoles); final var entities = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( memberNumber, fromValueDate, toValueDate); final var resources = Mapper.mapList(entities, HsOfficeCoopSharesTransactionResource.class, COOP_SHARES_ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.ok(resources); } @Override @Transactional public ResponseEntity<HsOfficeCoopSharesTransactionResource> addCoopSharesTransaction( final String currentUser, final String assumedRoles, @Valid final HsOfficeCoopSharesTransactionInsertResource body) { context.define(currentUser, assumedRoles); final var entityToSave = map( body, HsOfficeCoopSharesTransactionEntity.class, COOP_SHARES_RESOURCE_TO_ENTITY_POSTMAPPER); entityToSave.setUuid(UUID.randomUUID()); final var saved = coopSharesTransactionRepo.save(entityToSave); final var uri = MvcUriComponentsBuilder.fromController(getClass()) .path("/api/hs/office/CoopSharesTransactions/{id}") .buildAndExpand(entityToSave.getUuid()) .toUri(); final var mapped = map(saved, HsOfficeCoopSharesTransactionResource.class, COOP_SHARES_ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.created(uri).body(mapped); } final BiConsumer<HsOfficeCoopSharesTransactionEntity, HsOfficeCoopSharesTransactionResource> COOP_SHARES_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { // resource.setValidFrom(entity.getValidity().lower()); // if (entity.getValidity().hasUpperBound()) { // resource.setValidTo(entity.getValidity().upper().minusDays(1)); // } }; final BiConsumer<HsOfficeCoopSharesTransactionInsertResource, HsOfficeCoopSharesTransactionEntity> COOP_SHARES_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { // entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo())); }; } src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepository.java
@@ -15,8 +15,8 @@ @Query(""" SELECT st FROM HsOfficeCoopSharesTransactionEntity st WHERE (:memberNumber IS NULL OR st.membership.memberNumber = :memberNumber) AND (:fromValueDate IS NULL OR (st.valueDate >= :fromValueDate)) AND (:toValueDate IS NULL OR (st.valueDate <= :toValueDate)) AND ( CAST(:fromValueDate AS java.time.LocalDate) IS NULL OR (st.valueDate >= :fromValueDate)) AND ( CAST(:toValueDate AS java.time.LocalDate)IS NULL OR (st.valueDate <= :toValueDate)) ORDER BY st.membership.memberNumber, st.valueDate """) List<HsOfficeCoopSharesTransactionEntity> findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( src/main/resources/api-definition/hs-office/hs-office-coopshares-schemas.yaml
New file @@ -0,0 +1,54 @@ components: schemas: HsOfficeCoopSharesTransactionType: type: string enum: - ADJUSTMENT - SUBSCRIPTION - CANCELLATION; HsOfficeCoopSharesTransaction: type: object properties: uuid: type: string format: uuid transactionType: $ref: '#/components/schemas/HsOfficeCoopSharesTransactionType' sharesCount: type: integer valueDate: type: string format: date reference: type: string comment: type: string HsOfficeCoopSharesTransactionInsert: type: object properties: membershipUuid: type: string format: uuid nullable: false transactionType: $ref: '#/components/schemas/HsOfficeCoopSharesTransactionType' sharesCount: type: integer valueDate: type: string format: date reference: type: string comment: type: string required: - transactionType - sharesCount - valueDate - reference additionalProperties: false src/main/resources/api-definition/hs-office/hs-office-coopshares.yaml
New file @@ -0,0 +1,78 @@ get: summary: Returns a list of (optionally filtered) cooperative share transactions. description: Returns the list of (optionally filtered) cooperative share transactions which are visible to the current user or any of it's assumed roles. tags: - hs-office-coopShares operationId: listCoopShares parameters: - $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/assumedRoles' - name: membershipUuid in: query required: false schema: type: string format: uuid description: Optional UUID of the related membership. - name: memberNumber in: query required: false schema: type: integer description: Optional member number of the related membership. - name: fromValueDate in: query required: false schema: type: string format: date description: Optional value date range start (inclusive). - name: toValueDate in: query required: false schema: type: string format: date description: Optional value date range end (inclusive). responses: "200": description: OK content: 'application/json': schema: type: array items: $ref: './hs-office-coopshares-schemas.yaml#/components/schemas/HsOfficeCoopSharesTransaction' "401": $ref: './error-responses.yaml#/components/responses/Unauthorized' "403": $ref: './error-responses.yaml#/components/responses/Forbidden' post: summary: Adds a new cooperative share transaction. tags: - hs-office-coopShares operationId: addCoopSharesTransaction parameters: - $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/assumedRoles' requestBody: description: A JSON object describing the new cooperative shares transaction. required: true content: application/json: schema: $ref: '/hs-office-coopshares-schemas.yaml#/components/schemas/HsOfficeCoopSharesTransactionInsert' responses: "201": description: Created content: 'application/json': schema: $ref: './hs-office-coopshares-schemas.yaml#/components/schemas/HsOfficeCoopSharesTransaction' "401": $ref: './error-responses.yaml#/components/responses/Unauthorized' "403": $ref: './error-responses.yaml#/components/responses/Forbidden' "409": $ref: './error-responses.yaml#/components/responses/Conflict' src/main/resources/api-definition/hs-office/hs-office.yaml
@@ -78,3 +78,9 @@ /api/hs/office/memberships/{membershipUUID}: $ref: "./hs-office-memberships-with-uuid.yaml" # Coop Shares Transaction /api/hs/office/coopsharestransactions: $ref: "./hs-office-coopshares.yaml" src/main/resources/db/changelog/318-hs-office-coopshares-test-data.sql
@@ -25,7 +25,7 @@ into hs_office_coopsharestransaction(uuid, membershipuuid, transactiontype, valuedate, sharecount, reference, comment) values (uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2010-03-15', 2, 'ref '||givenMembershipNumber||'-1', 'initial subscription'), (uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2021-09-01', 24, 'ref '||givenMembershipNumber||'-2', 'subsscibing more'), (uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2021-09-01', 24, 'ref '||givenMembershipNumber||'-2', 'subscibing more'), (uuid_generate_v4(), membership.uuid, 'CANCELLATION', '2022-10-20', 12, 'ref '||givenMembershipNumber||'-3', 'cancelling some'); end; $$; --// src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java
New file @@ -0,0 +1,209 @@ package net.hostsharing.hsadminng.hs.office.coopshares; import io.restassured.RestAssured; import io.restassured.http.ContentType; import net.hostsharing.hsadminng.Accepts; import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository; import net.hostsharing.test.JpaAttempt; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import java.util.UUID; import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid; import static net.hostsharing.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.startsWith; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { HsadminNgApplication.class, JpaAttempt.class } ) @Transactional class HsOfficeCoopSharesTransactionControllerAcceptanceTest { @LocalServerPort private Integer port; @Autowired Context context; @Autowired Context contextMock; @Autowired HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; @Autowired HsOfficeMembershipRepository membershipRepo; @Autowired JpaAttempt jpaAttempt; @Autowired EntityManager em; @Nested @Accepts({ "CoopSharesTransaction:F(Find)" }) class ListCoopSharesTransactions { @Test void globalAdmin_canViewAllCoopSharesTransactions() { RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") .port(port) .when() .get("http://localhost/api/hs/office/coopsharestransactions") .then().log().all().assertThat() .statusCode(200) .contentType("application/json") .body("", hasSize(9)); // @formatter:on } @Test void globalAdmin_canFindCoopSharesTransactionsByMemberNumber() { RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") .port(port) .when() .get("http://localhost/api/hs/office/coopsharestransactions?memberNumber=10002") .then().log().all().assertThat() .statusCode(200) .contentType("application/json") .body("", lenientlyEquals(""" [ { "transactionType": "SUBSCRIPTION", "sharesCount": 2, "valueDate": "2010-03-15", "reference": "ref 10002-1", "comment": "initial subscription" }, { "transactionType": "SUBSCRIPTION", "sharesCount": 24, "valueDate": "2021-09-01", "reference": "ref 10002-2", "comment": "subscibing more" }, { "transactionType": "CANCELLATION;", "sharesCount": 12, "valueDate": "2022-10-20", "reference": "ref 10002-3", "comment": "cancelling some" } ] """)); // @formatter:on } @Test void globalAdmin_canFindCoopSharesTransactionsByMemberNumberAndDateRange() { RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") .port(port) .when() .get("http://localhost/api/hs/office/coopsharestransactions?memberNumber=10002&fromValueDate=2020-01-01&toValueDate=2021-12-31") .then().log().all().assertThat() .statusCode(200) .contentType("application/json") .body("", lenientlyEquals(""" [ { "transactionType": "SUBSCRIPTION", "sharesCount": 24, "valueDate": "2021-09-01", "reference": "ref 10002-2", "comment": "subscibing more" } ] """)); // @formatter:on } } @Nested @Accepts({ "CoopSharesTransaction:C(Create)" }) class AddCoopSharesTransaction { @Test void globalAdmin_canAddCoopSharesTransaction() { context.define("superuser-alex@hostsharing.net"); final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10001) .get(0); final var location = RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") .contentType(ContentType.JSON) .body(""" { "membershipUuid": "%s", "transactionType": "SUBSCRIPTION", "sharesCount": 8, "valueDate": "2022-10-13", "reference": "temp ref A", "comment": "just some test coop shares transaction" } """.formatted(givenMembership.getUuid())) .port(port) .when() .post("http://localhost/api/hs/office/coopsharestransactions") .then().log().all().assertThat() .statusCode(201) .contentType(ContentType.JSON) .body("uuid", isUuidValid()) .body("", lenientlyEquals(""" { "transactionType": "SUBSCRIPTION", "sharesCount": 0, "valueDate": "2022-10-13", "reference": "temp ref A", "comment": "just some test coop shares transaction" } """)) .header("Location", startsWith("http://localhost")) .extract().header("Location"); // @formatter:on // finally, the new coopSharesTransaction can be accessed under the generated UUID final var newUserUuid = UUID.fromString( location.substring(location.lastIndexOf('/') + 1)); assertThat(newUserUuid).isNotNull(); } // TODO.test: move validation tests to a ...WebMvcTest @Test void globalAdmin_canNotAddCoopSharesTransactionWhenMembershipUuidIsMissing() { } } @BeforeEach @AfterEach void cleanup() { jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net", null); // HsOfficeCoopSharesTransactionEntity respectively hs_office_coopsharestransaction_rv // cannot be deleted at all, but the underlying table record can be deleted. em.createNativeQuery("delete from hs_office_coopsharestransaction where reference like 'temp %'") .executeUpdate(); }).assertSuccessful(); } }