hs-office-coopshares: add non-negative validation
| | |
| | | import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.SUBSCRIPTION; |
| | | |
| | | @RestController |
| | | |
| | | public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopSharesApi { |
| | | |
| | | @Autowired |
| | |
| | | |
| | | final var uri = |
| | | MvcUriComponentsBuilder.fromController(getClass()) |
| | | .path("/api/hs/office/CoopSharesTransactions/{id}") |
| | | .path("/api/hs/office/coopsharestransactions/{id}") |
| | | .buildAndExpand(entityToSave.getUuid()) |
| | | .toUri(); |
| | | final var mapped = map(saved, HsOfficeCoopSharesTransactionResource.class); |
| | |
| | | final var violations = new ArrayList<String>(); |
| | | validateSubscriptionTransaction(requestBody, violations); |
| | | validateCancellationTransaction(requestBody, violations); |
| | | validateSharesCount(requestBody, violations); |
| | | validateshareCount(requestBody, violations); |
| | | if (violations.size() > 0) { |
| | | throw new ValidationException("[" + join(", ", violations) + "]"); |
| | | } |
| | |
| | | final HsOfficeCoopSharesTransactionInsertResource requestBody, |
| | | final ArrayList<String> violations) { |
| | | if (requestBody.getTransactionType() == SUBSCRIPTION |
| | | && requestBody.getSharesCount() < 0) { |
| | | violations.add("for %s, sharesCount must be positive but is \"%d\"".formatted( |
| | | requestBody.getTransactionType(), requestBody.getSharesCount())); |
| | | && requestBody.getShareCount() < 0) { |
| | | violations.add("for %s, shareCount must be positive but is \"%d\"".formatted( |
| | | requestBody.getTransactionType(), requestBody.getShareCount())); |
| | | } |
| | | } |
| | | |
| | |
| | | final HsOfficeCoopSharesTransactionInsertResource requestBody, |
| | | final ArrayList<String> violations) { |
| | | if (requestBody.getTransactionType() == CANCELLATION |
| | | && requestBody.getSharesCount() > 0) { |
| | | violations.add("for %s, sharesCount must be negative but is \"%d\"".formatted( |
| | | requestBody.getTransactionType(), requestBody.getSharesCount())); |
| | | && requestBody.getShareCount() > 0) { |
| | | violations.add("for %s, shareCount must be negative but is \"%d\"".formatted( |
| | | requestBody.getTransactionType(), requestBody.getShareCount())); |
| | | } |
| | | } |
| | | |
| | | private static void validateSharesCount( |
| | | private static void validateshareCount( |
| | | final HsOfficeCoopSharesTransactionInsertResource requestBody, |
| | | final ArrayList<String> violations) { |
| | | if (requestBody.getSharesCount() == 0) { |
| | | violations.add("sharesCount must not be 0 but is \"%d\"".formatted( |
| | | requestBody.getSharesCount())); |
| | | if (requestBody.getShareCount() == 0) { |
| | | violations.add("shareCount must not be 0 but is \"%d\"".formatted( |
| | | requestBody.getShareCount())); |
| | | } |
| | | } |
| | | |
| | |
| | | format: uuid |
| | | transactionType: |
| | | $ref: '#/components/schemas/HsOfficeCoopSharesTransactionType' |
| | | sharesCount: |
| | | shareCount: |
| | | type: integer |
| | | valueDate: |
| | | type: string |
| | |
| | | nullable: false |
| | | transactionType: |
| | | $ref: '#/components/schemas/HsOfficeCoopSharesTransactionType' |
| | | sharesCount: |
| | | shareCount: |
| | | type: integer |
| | | valueDate: |
| | | type: string |
| | |
| | | required: |
| | | - membershipUuid |
| | | - transactionType |
| | | - sharesCount |
| | | - shareCount |
| | | - valueDate |
| | | - reference |
| | | additionalProperties: false |
| | |
| | | ); |
| | | --// |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopshares-SHARE-COUNT-CONSTRAINT:1 endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | |
| | | create or replace function checkSharesByMembershipUuid(forMembershipUuid UUID, newShareCount integer) |
| | | returns boolean |
| | | language plpgsql as $$ |
| | | declare |
| | | currentShareCount integer; |
| | | totalShareCount integer; |
| | | begin |
| | | select sum(cst.shareCount) |
| | | from hs_office_coopsharestransaction cst |
| | | where cst.membershipUuid = forMembershipUuid |
| | | into currentShareCount; |
| | | totalShareCount := currentShareCount + newShareCount; |
| | | if totalShareCount < 0 then |
| | | raise exception '[400] coop shares transaction would result in a negative number of shares'; |
| | | end if; |
| | | return true; |
| | | end; $$; |
| | | |
| | | alter table hs_office_coopsharestransaction |
| | | add constraint hs_office_coopshares_positive |
| | | check ( checkSharesByMembershipUuid(membershipUuid, shareCount) ); |
| | | |
| | | --// |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopshares-MAIN-TABLE-JOURNAL:1 endDelimiter:--// |
| | |
| | | [ |
| | | { |
| | | "transactionType": "SUBSCRIPTION", |
| | | "sharesCount": 4, |
| | | "shareCount": 4, |
| | | "valueDate": "2010-03-15", |
| | | "reference": "ref 10002-1", |
| | | "comment": "initial subscription" |
| | | }, |
| | | { |
| | | "transactionType": "CANCELLATION", |
| | | "sharesCount": -2, |
| | | "shareCount": -2, |
| | | "valueDate": "2021-09-01", |
| | | "reference": "ref 10002-2", |
| | | "comment": "cancelling some" |
| | | }, |
| | | { |
| | | "transactionType": "ADJUSTMENT", |
| | | "sharesCount": 2, |
| | | "shareCount": 2, |
| | | "valueDate": "2022-10-20", |
| | | "reference": "ref 10002-3", |
| | | "comment": "some adjustment" |
| | |
| | | [ |
| | | { |
| | | "transactionType": "CANCELLATION", |
| | | "sharesCount": -2, |
| | | "shareCount": -2, |
| | | "valueDate": "2021-09-01", |
| | | "reference": "ref 10002-2", |
| | | "comment": "cancelling some" |
| | |
| | | { |
| | | "membershipUuid": "%s", |
| | | "transactionType": "SUBSCRIPTION", |
| | | "sharesCount": 8, |
| | | "shareCount": 8, |
| | | "valueDate": "2022-10-13", |
| | | "reference": "temp ref A", |
| | | "comment": "just some test coop shares transaction" |
| | |
| | | .body("", lenientlyEquals(""" |
| | | { |
| | | "transactionType": "SUBSCRIPTION", |
| | | "sharesCount": 0, |
| | | "shareCount": 8, |
| | | "valueDate": "2022-10-13", |
| | | "reference": "temp ref A", |
| | | "comment": "just some test coop shares transaction" |
| | |
| | | location.substring(location.lastIndexOf('/') + 1)); |
| | | assertThat(newUserUuid).isNotNull(); |
| | | } |
| | | |
| | | @Test |
| | | void globalAdmin_canNotCancelMoreSharesThanCurrentlySubscribed() { |
| | | |
| | | 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": "CANCELLATION", |
| | | "shareCount": -80, |
| | | "valueDate": "2022-10-13", |
| | | "reference": "temp ref X", |
| | | "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(400) |
| | | .contentType(ContentType.JSON) |
| | | .body("", lenientlyEquals(""" |
| | | { |
| | | "status": 400, |
| | | "error": "Bad Request", |
| | | "message": "ERROR: [400] coop shares transaction would result in a negative number of shares" |
| | | } |
| | | """)); // @formatter:on |
| | | } |
| | | } |
| | | |
| | | @BeforeEach |
| | |
| | | { |
| | | "membershipUuid": "%s", |
| | | "transactionType": "SUBSCRIPTION", |
| | | "sharesCount": 8, |
| | | "shareCount": 8, |
| | | "valueDate": "2022-10-13", |
| | | "reference": "valid reference", |
| | | "comment": "valid comment" |
| | |
| | | SHARES_COUNT_FOR_SUBSCRIPTION_MUST_BE_POSITIVE( |
| | | requestBody -> requestBody |
| | | .with("transactionType", "SUBSCRIPTION") |
| | | .with("sharesCount", -1), |
| | | "[for SUBSCRIPTION, sharesCount must be positive but is \"-1\"]"), |
| | | .with("shareCount", -1), |
| | | "[for SUBSCRIPTION, shareCount must be positive but is \"-1\"]"), |
| | | |
| | | SHARES_COUNT_FOR_CANCELLATION_MUST_BE_NEGATIVE( |
| | | requestBody -> requestBody |
| | | .with("transactionType", "CANCELLATION") |
| | | .with("sharesCount", 1), |
| | | "[for CANCELLATION, sharesCount must be negative but is \"1\"]"), |
| | | .with("shareCount", 1), |
| | | "[for CANCELLATION, shareCount must be negative but is \"1\"]"), |
| | | |
| | | SHARES_COUNT_MUST_NOT_BE_NULL( |
| | | requestBody -> requestBody |
| | | .with("transactionType", "ADJUSTMENT") |
| | | .with("sharesCount", 0), |
| | | "[sharesCount must not be 0 but is \"0\"]"), |
| | | .with("shareCount", 0), |
| | | "[shareCount must not be 0 but is \"0\"]"), |
| | | |
| | | REFERENCE_MISSING( |
| | | requestBody -> requestBody.without("reference"), |
| | |
| | | |
| | | class HsOfficeCoopSharesTransactionEntityTest { |
| | | |
| | | final HsOfficeCoopSharesTransactionEntity givenSepaMandate = HsOfficeCoopSharesTransactionEntity.builder() |
| | | final HsOfficeCoopSharesTransactionEntity givenCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder() |
| | | .membership(testMembership) |
| | | .reference("some-ref") |
| | | .valueDate(LocalDate.parse("2020-01-01")) |
| | |
| | | |
| | | @Test |
| | | void toStringContainsAlmostAllPropertiesAccount() { |
| | | final var result = givenSepaMandate.toString(); |
| | | final var result = givenCoopSharesTransaction.toString(); |
| | | |
| | | assertThat(result).isEqualTo("CoopShareTransaction(300001, 2020-01-01, SUBSCRIPTION, 4, some-ref)"); |
| | | } |
| | | |
| | | @Test |
| | | void toShortStringContainsOnlyMemberNumberAndSharesCountOnly() { |
| | | final var result = givenSepaMandate.toShortString(); |
| | | void toShortStringContainsOnlyMemberNumberAndshareCountOnly() { |
| | | final var result = givenCoopSharesTransaction.toShortString(); |
| | | |
| | | assertThat(result).isEqualTo("300001+4"); |
| | | } |