hs-office-coopshares: add non-negative validation
This commit is contained in:
parent
22bca20c49
commit
dc0835fa25
@ -26,7 +26,6 @@ import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOffic
|
|||||||
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.SUBSCRIPTION;
|
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.SUBSCRIPTION;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
|
||||||
public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopSharesApi {
|
public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopSharesApi {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -71,7 +70,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
|||||||
|
|
||||||
final var uri =
|
final var uri =
|
||||||
MvcUriComponentsBuilder.fromController(getClass())
|
MvcUriComponentsBuilder.fromController(getClass())
|
||||||
.path("/api/hs/office/CoopSharesTransactions/{id}")
|
.path("/api/hs/office/coopsharestransactions/{id}")
|
||||||
.buildAndExpand(entityToSave.getUuid())
|
.buildAndExpand(entityToSave.getUuid())
|
||||||
.toUri();
|
.toUri();
|
||||||
final var mapped = map(saved, HsOfficeCoopSharesTransactionResource.class);
|
final var mapped = map(saved, HsOfficeCoopSharesTransactionResource.class);
|
||||||
@ -82,7 +81,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
|||||||
final var violations = new ArrayList<String>();
|
final var violations = new ArrayList<String>();
|
||||||
validateSubscriptionTransaction(requestBody, violations);
|
validateSubscriptionTransaction(requestBody, violations);
|
||||||
validateCancellationTransaction(requestBody, violations);
|
validateCancellationTransaction(requestBody, violations);
|
||||||
validateSharesCount(requestBody, violations);
|
validateshareCount(requestBody, violations);
|
||||||
if (violations.size() > 0) {
|
if (violations.size() > 0) {
|
||||||
throw new ValidationException("[" + join(", ", violations) + "]");
|
throw new ValidationException("[" + join(", ", violations) + "]");
|
||||||
}
|
}
|
||||||
@ -92,9 +91,9 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
|||||||
final HsOfficeCoopSharesTransactionInsertResource requestBody,
|
final HsOfficeCoopSharesTransactionInsertResource requestBody,
|
||||||
final ArrayList<String> violations) {
|
final ArrayList<String> violations) {
|
||||||
if (requestBody.getTransactionType() == SUBSCRIPTION
|
if (requestBody.getTransactionType() == SUBSCRIPTION
|
||||||
&& requestBody.getSharesCount() < 0) {
|
&& requestBody.getShareCount() < 0) {
|
||||||
violations.add("for %s, sharesCount must be positive but is \"%d\"".formatted(
|
violations.add("for %s, shareCount must be positive but is \"%d\"".formatted(
|
||||||
requestBody.getTransactionType(), requestBody.getSharesCount()));
|
requestBody.getTransactionType(), requestBody.getShareCount()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,18 +101,18 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
|||||||
final HsOfficeCoopSharesTransactionInsertResource requestBody,
|
final HsOfficeCoopSharesTransactionInsertResource requestBody,
|
||||||
final ArrayList<String> violations) {
|
final ArrayList<String> violations) {
|
||||||
if (requestBody.getTransactionType() == CANCELLATION
|
if (requestBody.getTransactionType() == CANCELLATION
|
||||||
&& requestBody.getSharesCount() > 0) {
|
&& requestBody.getShareCount() > 0) {
|
||||||
violations.add("for %s, sharesCount must be negative but is \"%d\"".formatted(
|
violations.add("for %s, shareCount must be negative but is \"%d\"".formatted(
|
||||||
requestBody.getTransactionType(), requestBody.getSharesCount()));
|
requestBody.getTransactionType(), requestBody.getShareCount()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateSharesCount(
|
private static void validateshareCount(
|
||||||
final HsOfficeCoopSharesTransactionInsertResource requestBody,
|
final HsOfficeCoopSharesTransactionInsertResource requestBody,
|
||||||
final ArrayList<String> violations) {
|
final ArrayList<String> violations) {
|
||||||
if (requestBody.getSharesCount() == 0) {
|
if (requestBody.getShareCount() == 0) {
|
||||||
violations.add("sharesCount must not be 0 but is \"%d\"".formatted(
|
violations.add("shareCount must not be 0 but is \"%d\"".formatted(
|
||||||
requestBody.getSharesCount()));
|
requestBody.getShareCount()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ components:
|
|||||||
format: uuid
|
format: uuid
|
||||||
transactionType:
|
transactionType:
|
||||||
$ref: '#/components/schemas/HsOfficeCoopSharesTransactionType'
|
$ref: '#/components/schemas/HsOfficeCoopSharesTransactionType'
|
||||||
sharesCount:
|
shareCount:
|
||||||
type: integer
|
type: integer
|
||||||
valueDate:
|
valueDate:
|
||||||
type: string
|
type: string
|
||||||
@ -37,7 +37,7 @@ components:
|
|||||||
nullable: false
|
nullable: false
|
||||||
transactionType:
|
transactionType:
|
||||||
$ref: '#/components/schemas/HsOfficeCoopSharesTransactionType'
|
$ref: '#/components/schemas/HsOfficeCoopSharesTransactionType'
|
||||||
sharesCount:
|
shareCount:
|
||||||
type: integer
|
type: integer
|
||||||
valueDate:
|
valueDate:
|
||||||
type: string
|
type: string
|
||||||
@ -51,7 +51,7 @@ components:
|
|||||||
required:
|
required:
|
||||||
- membershipUuid
|
- membershipUuid
|
||||||
- transactionType
|
- transactionType
|
||||||
- sharesCount
|
- shareCount
|
||||||
- valueDate
|
- valueDate
|
||||||
- reference
|
- reference
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
@ -20,6 +20,33 @@ create table if not exists hs_office_coopsharestransaction
|
|||||||
);
|
);
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--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:--//
|
--changeset hs-office-coopshares-MAIN-TABLE-JOURNAL:1 endDelimiter:--//
|
||||||
|
@ -92,21 +92,21 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"transactionType": "SUBSCRIPTION",
|
"transactionType": "SUBSCRIPTION",
|
||||||
"sharesCount": 4,
|
"shareCount": 4,
|
||||||
"valueDate": "2010-03-15",
|
"valueDate": "2010-03-15",
|
||||||
"reference": "ref 10002-1",
|
"reference": "ref 10002-1",
|
||||||
"comment": "initial subscription"
|
"comment": "initial subscription"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"transactionType": "CANCELLATION",
|
"transactionType": "CANCELLATION",
|
||||||
"sharesCount": -2,
|
"shareCount": -2,
|
||||||
"valueDate": "2021-09-01",
|
"valueDate": "2021-09-01",
|
||||||
"reference": "ref 10002-2",
|
"reference": "ref 10002-2",
|
||||||
"comment": "cancelling some"
|
"comment": "cancelling some"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"transactionType": "ADJUSTMENT",
|
"transactionType": "ADJUSTMENT",
|
||||||
"sharesCount": 2,
|
"shareCount": 2,
|
||||||
"valueDate": "2022-10-20",
|
"valueDate": "2022-10-20",
|
||||||
"reference": "ref 10002-3",
|
"reference": "ref 10002-3",
|
||||||
"comment": "some adjustment"
|
"comment": "some adjustment"
|
||||||
@ -136,7 +136,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"transactionType": "CANCELLATION",
|
"transactionType": "CANCELLATION",
|
||||||
"sharesCount": -2,
|
"shareCount": -2,
|
||||||
"valueDate": "2021-09-01",
|
"valueDate": "2021-09-01",
|
||||||
"reference": "ref 10002-2",
|
"reference": "ref 10002-2",
|
||||||
"comment": "cancelling some"
|
"comment": "cancelling some"
|
||||||
@ -165,7 +165,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest {
|
|||||||
{
|
{
|
||||||
"membershipUuid": "%s",
|
"membershipUuid": "%s",
|
||||||
"transactionType": "SUBSCRIPTION",
|
"transactionType": "SUBSCRIPTION",
|
||||||
"sharesCount": 8,
|
"shareCount": 8,
|
||||||
"valueDate": "2022-10-13",
|
"valueDate": "2022-10-13",
|
||||||
"reference": "temp ref A",
|
"reference": "temp ref A",
|
||||||
"comment": "just some test coop shares transaction"
|
"comment": "just some test coop shares transaction"
|
||||||
@ -181,7 +181,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest {
|
|||||||
.body("", lenientlyEquals("""
|
.body("", lenientlyEquals("""
|
||||||
{
|
{
|
||||||
"transactionType": "SUBSCRIPTION",
|
"transactionType": "SUBSCRIPTION",
|
||||||
"sharesCount": 0,
|
"shareCount": 8,
|
||||||
"valueDate": "2022-10-13",
|
"valueDate": "2022-10-13",
|
||||||
"reference": "temp ref A",
|
"reference": "temp ref A",
|
||||||
"comment": "just some test coop shares transaction"
|
"comment": "just some test coop shares transaction"
|
||||||
@ -195,6 +195,42 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest {
|
|||||||
location.substring(location.lastIndexOf('/') + 1));
|
location.substring(location.lastIndexOf('/') + 1));
|
||||||
assertThat(newUserUuid).isNotNull();
|
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
|
@BeforeEach
|
||||||
|
@ -35,7 +35,7 @@ class HsOfficeCoopSharesTransactionControllerRestTest {
|
|||||||
{
|
{
|
||||||
"membershipUuid": "%s",
|
"membershipUuid": "%s",
|
||||||
"transactionType": "SUBSCRIPTION",
|
"transactionType": "SUBSCRIPTION",
|
||||||
"sharesCount": 8,
|
"shareCount": 8,
|
||||||
"valueDate": "2022-10-13",
|
"valueDate": "2022-10-13",
|
||||||
"reference": "valid reference",
|
"reference": "valid reference",
|
||||||
"comment": "valid comment"
|
"comment": "valid comment"
|
||||||
@ -58,20 +58,20 @@ class HsOfficeCoopSharesTransactionControllerRestTest {
|
|||||||
SHARES_COUNT_FOR_SUBSCRIPTION_MUST_BE_POSITIVE(
|
SHARES_COUNT_FOR_SUBSCRIPTION_MUST_BE_POSITIVE(
|
||||||
requestBody -> requestBody
|
requestBody -> requestBody
|
||||||
.with("transactionType", "SUBSCRIPTION")
|
.with("transactionType", "SUBSCRIPTION")
|
||||||
.with("sharesCount", -1),
|
.with("shareCount", -1),
|
||||||
"[for SUBSCRIPTION, sharesCount must be positive but is \"-1\"]"),
|
"[for SUBSCRIPTION, shareCount must be positive but is \"-1\"]"),
|
||||||
|
|
||||||
SHARES_COUNT_FOR_CANCELLATION_MUST_BE_NEGATIVE(
|
SHARES_COUNT_FOR_CANCELLATION_MUST_BE_NEGATIVE(
|
||||||
requestBody -> requestBody
|
requestBody -> requestBody
|
||||||
.with("transactionType", "CANCELLATION")
|
.with("transactionType", "CANCELLATION")
|
||||||
.with("sharesCount", 1),
|
.with("shareCount", 1),
|
||||||
"[for CANCELLATION, sharesCount must be negative but is \"1\"]"),
|
"[for CANCELLATION, shareCount must be negative but is \"1\"]"),
|
||||||
|
|
||||||
SHARES_COUNT_MUST_NOT_BE_NULL(
|
SHARES_COUNT_MUST_NOT_BE_NULL(
|
||||||
requestBody -> requestBody
|
requestBody -> requestBody
|
||||||
.with("transactionType", "ADJUSTMENT")
|
.with("transactionType", "ADJUSTMENT")
|
||||||
.with("sharesCount", 0),
|
.with("shareCount", 0),
|
||||||
"[sharesCount must not be 0 but is \"0\"]"),
|
"[shareCount must not be 0 but is \"0\"]"),
|
||||||
|
|
||||||
REFERENCE_MISSING(
|
REFERENCE_MISSING(
|
||||||
requestBody -> requestBody.without("reference"),
|
requestBody -> requestBody.without("reference"),
|
||||||
|
@ -9,7 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
|
|
||||||
class HsOfficeCoopSharesTransactionEntityTest {
|
class HsOfficeCoopSharesTransactionEntityTest {
|
||||||
|
|
||||||
final HsOfficeCoopSharesTransactionEntity givenSepaMandate = HsOfficeCoopSharesTransactionEntity.builder()
|
final HsOfficeCoopSharesTransactionEntity givenCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder()
|
||||||
.membership(testMembership)
|
.membership(testMembership)
|
||||||
.reference("some-ref")
|
.reference("some-ref")
|
||||||
.valueDate(LocalDate.parse("2020-01-01"))
|
.valueDate(LocalDate.parse("2020-01-01"))
|
||||||
@ -19,14 +19,14 @@ class HsOfficeCoopSharesTransactionEntityTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void toStringContainsAlmostAllPropertiesAccount() {
|
void toStringContainsAlmostAllPropertiesAccount() {
|
||||||
final var result = givenSepaMandate.toString();
|
final var result = givenCoopSharesTransaction.toString();
|
||||||
|
|
||||||
assertThat(result).isEqualTo("CoopShareTransaction(300001, 2020-01-01, SUBSCRIPTION, 4, some-ref)");
|
assertThat(result).isEqualTo("CoopShareTransaction(300001, 2020-01-01, SUBSCRIPTION, 4, some-ref)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void toShortStringContainsOnlyMemberNumberAndSharesCountOnly() {
|
void toShortStringContainsOnlyMemberNumberAndshareCountOnly() {
|
||||||
final var result = givenSepaMandate.toShortString();
|
final var result = givenCoopSharesTransaction.toShortString();
|
||||||
|
|
||||||
assertThat(result).isEqualTo("300001+4");
|
assertThat(result).isEqualTo("300001+4");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user