hs-office-coopassets, no get API endpoints yet
14 files added
4 files modified
New file |
| | |
| | | package net.hostsharing.hsadminng.hs.office.coopassets; |
| | | |
| | | import net.hostsharing.hsadminng.context.Context; |
| | | import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopAssetsApi; |
| | | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopAssetsTransactionInsertResource; |
| | | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopAssetsTransactionResource; |
| | | import net.hostsharing.hsadminng.mapper.Mapper; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | import org.springframework.format.annotation.DateTimeFormat.ISO; |
| | | 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.validation.Valid; |
| | | import javax.validation.ValidationException; |
| | | import java.time.LocalDate; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.UUID; |
| | | |
| | | import static java.lang.String.join; |
| | | import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopAssetsTransactionTypeResource.*; |
| | | import static net.hostsharing.hsadminng.mapper.Mapper.map; |
| | | |
| | | @RestController |
| | | public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAssetsApi { |
| | | |
| | | @Autowired |
| | | private Context context; |
| | | |
| | | @Autowired |
| | | private HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo; |
| | | |
| | | @Override |
| | | @Transactional(readOnly = true) |
| | | public ResponseEntity<List<HsOfficeCoopAssetsTransactionResource>> listCoopAssets( |
| | | final String currentUser, |
| | | final String assumedRoles, |
| | | final UUID membershipUuid, |
| | | final @DateTimeFormat(iso = ISO.DATE) LocalDate fromValueDate, |
| | | final @DateTimeFormat(iso = ISO.DATE) LocalDate toValueDate) { |
| | | context.define(currentUser, assumedRoles); |
| | | |
| | | final var entities = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( |
| | | membershipUuid, |
| | | fromValueDate, |
| | | toValueDate); |
| | | |
| | | final var resources = Mapper.mapList(entities, HsOfficeCoopAssetsTransactionResource.class); |
| | | return ResponseEntity.ok(resources); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional |
| | | public ResponseEntity<HsOfficeCoopAssetsTransactionResource> addCoopAssetsTransaction( |
| | | final String currentUser, |
| | | final String assumedRoles, |
| | | @Valid final HsOfficeCoopAssetsTransactionInsertResource requestBody) { |
| | | |
| | | context.define(currentUser, assumedRoles); |
| | | validate(requestBody); |
| | | |
| | | final var entityToSave = map(requestBody, HsOfficeCoopAssetsTransactionEntity.class); |
| | | entityToSave.setUuid(UUID.randomUUID()); |
| | | |
| | | final var saved = coopAssetsTransactionRepo.save(entityToSave); |
| | | |
| | | final var uri = |
| | | MvcUriComponentsBuilder.fromController(getClass()) |
| | | .path("/api/hs/office/coopassetstransactions/{id}") |
| | | .buildAndExpand(entityToSave.getUuid()) |
| | | .toUri(); |
| | | final var mapped = map(saved, HsOfficeCoopAssetsTransactionResource.class); |
| | | return ResponseEntity.created(uri).body(mapped); |
| | | } |
| | | |
| | | private void validate(final HsOfficeCoopAssetsTransactionInsertResource requestBody) { |
| | | final var violations = new ArrayList<String>(); |
| | | validateDebitTransaction(requestBody, violations); |
| | | validateCreditTransaction(requestBody, violations); |
| | | validateAssetValue(requestBody, violations); |
| | | if (violations.size() > 0) { |
| | | throw new ValidationException("[" + join(", ", violations) + "]"); |
| | | } |
| | | } |
| | | |
| | | private static void validateDebitTransaction( |
| | | final HsOfficeCoopAssetsTransactionInsertResource requestBody, |
| | | final ArrayList<String> violations) { |
| | | if (List.of(DEPOSIT, ADOPTION).contains(requestBody.getTransactionType()) |
| | | && requestBody.getAssetValue().signum() < 0) { |
| | | violations.add("for %s, assetValue must be positive but is \"%.2f\"".formatted( |
| | | requestBody.getTransactionType(), requestBody.getAssetValue())); |
| | | } |
| | | } |
| | | |
| | | private static void validateCreditTransaction( |
| | | final HsOfficeCoopAssetsTransactionInsertResource requestBody, |
| | | final ArrayList<String> violations) { |
| | | if (List.of(DISBURSAL, TRANSFER, CLEARING, LOSS).contains(requestBody.getTransactionType()) |
| | | && requestBody.getAssetValue().signum() > 0) { |
| | | violations.add("for %s, assetValue must be negative but is \"%.2f\"".formatted( |
| | | requestBody.getTransactionType(), requestBody.getAssetValue())); |
| | | } |
| | | } |
| | | |
| | | private static void validateAssetValue( |
| | | final HsOfficeCoopAssetsTransactionInsertResource requestBody, |
| | | final ArrayList<String> violations) { |
| | | if (requestBody.getAssetValue().signum() == 0) { |
| | | violations.add("assetValue must not be 0 but is \"%.2f\"".formatted( |
| | | requestBody.getAssetValue())); |
| | | } |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package net.hostsharing.hsadminng.hs.office.coopassets; |
| | | |
| | | import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType; |
| | | import lombok.*; |
| | | import net.hostsharing.hsadminng.errors.DisplayName; |
| | | import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; |
| | | import net.hostsharing.hsadminng.stringify.Stringify; |
| | | import net.hostsharing.hsadminng.stringify.Stringifyable; |
| | | import org.hibernate.annotations.Type; |
| | | import org.hibernate.annotations.TypeDef; |
| | | |
| | | import javax.persistence.*; |
| | | import java.math.BigDecimal; |
| | | import java.text.DecimalFormat; |
| | | import java.time.LocalDate; |
| | | import java.util.UUID; |
| | | |
| | | import static net.hostsharing.hsadminng.stringify.Stringify.stringify; |
| | | |
| | | @Entity |
| | | @Table(name = "hs_office_coopassetstransaction_rv") |
| | | @TypeDef( |
| | | name = "pgsql_enum", |
| | | typeClass = PostgreSQLEnumType.class |
| | | ) |
| | | @Getter |
| | | @Setter |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | @DisplayName("CoopAssetsTransaction") |
| | | public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable { |
| | | |
| | | private static Stringify<HsOfficeCoopAssetsTransactionEntity> stringify = stringify(HsOfficeCoopAssetsTransactionEntity.class) |
| | | .withProp(e -> e.getMembership().getMemberNumber()) |
| | | .withProp(HsOfficeCoopAssetsTransactionEntity::getValueDate) |
| | | .withProp(HsOfficeCoopAssetsTransactionEntity::getTransactionType) |
| | | .withProp(HsOfficeCoopAssetsTransactionEntity::getAssetValue) |
| | | .withProp(HsOfficeCoopAssetsTransactionEntity::getReference) |
| | | .withSeparator(", ") |
| | | .quotedValues(false); |
| | | |
| | | private @Id UUID uuid; |
| | | |
| | | @ManyToOne |
| | | @JoinColumn(name = "membershipuuid") |
| | | private HsOfficeMembershipEntity membership; |
| | | |
| | | @Column(name = "transactiontype") |
| | | @Enumerated(EnumType.STRING) |
| | | @Type( type = "pgsql_enum" ) |
| | | private HsOfficeCoopAssetsTransactionType transactionType; |
| | | |
| | | @Column(name = "valuedate") |
| | | private LocalDate valueDate; |
| | | |
| | | @Column(name = "assetvalue") |
| | | private BigDecimal assetValue; |
| | | |
| | | @Column(name = "reference") |
| | | private String reference; |
| | | |
| | | @Column(name = "comment") |
| | | private String comment; |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return stringify.apply(this); |
| | | } |
| | | |
| | | @Override |
| | | public String toShortString() { |
| | | return membership.getMemberNumber() + new DecimalFormat("+0.00").format(assetValue); |
| | | } |
| | | } |
New file |
| | |
| | | package net.hostsharing.hsadminng.hs.office.coopassets; |
| | | |
| | | import net.hostsharing.hsadminng.hs.office.coopshares.HsOfficeCoopSharesTransactionEntity; |
| | | import org.springframework.data.jpa.repository.Query; |
| | | import org.springframework.data.repository.Repository; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.util.List; |
| | | import java.util.Optional; |
| | | import java.util.UUID; |
| | | |
| | | public interface HsOfficeCoopAssetsTransactionRepository extends Repository<HsOfficeCoopAssetsTransactionEntity, UUID> { |
| | | |
| | | Optional<HsOfficeCoopAssetsTransactionEntity> findByUuid(UUID id); |
| | | |
| | | @Query(""" |
| | | SELECT at FROM HsOfficeCoopAssetsTransactionEntity at |
| | | WHERE ( CAST(:membershipUuid AS org.hibernate.type.UUIDCharType) IS NULL OR at.membership.uuid = :membershipUuid) |
| | | AND ( CAST(:fromValueDate AS java.time.LocalDate) IS NULL OR (at.valueDate >= :fromValueDate)) |
| | | AND ( CAST(:toValueDate AS java.time.LocalDate)IS NULL OR (at.valueDate <= :toValueDate)) |
| | | ORDER BY at.membership.memberNumber, at.valueDate |
| | | """) |
| | | List<HsOfficeCoopAssetsTransactionEntity> findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( |
| | | UUID membershipUuid, LocalDate fromValueDate, LocalDate toValueDate); |
| | | |
| | | HsOfficeCoopAssetsTransactionEntity save(final HsOfficeCoopAssetsTransactionEntity entity); |
| | | |
| | | long count(); |
| | | } |
New file |
| | |
| | | package net.hostsharing.hsadminng.hs.office.coopassets; |
| | | |
| | | public enum HsOfficeCoopAssetsTransactionType { |
| | | ADJUSTMENT, DEPOSIT, DISBURSAL, TRANSFER, ADOPTION, CLEARING, LOSS |
| | | } |
| | |
| | | - type: array => java.util.List |
| | | - type: string:uuid => java.util.UUID |
| | | - type: string:format => java.lang.String |
| | | - type: number:currency => java.math.BigDecimal |
| | | |
| | | paths: |
| | | /api/hs/office/partners/{partnerUUID}: |
New file |
| | |
| | | |
| | | components: |
| | | |
| | | schemas: |
| | | |
| | | HsOfficeCoopAssetsTransactionType: |
| | | type: string |
| | | enum: |
| | | - ADJUSTMENT |
| | | - DEPOSIT |
| | | - DISBURSAL |
| | | - TRANSFER |
| | | - ADOPTION |
| | | - CLEARING |
| | | - LOSS |
| | | |
| | | HsOfficeCoopAssetsTransaction: |
| | | type: object |
| | | properties: |
| | | uuid: |
| | | type: string |
| | | format: uuid |
| | | transactionType: |
| | | $ref: '#/components/schemas/HsOfficeCoopAssetsTransactionType' |
| | | assetValue: |
| | | type: number |
| | | format: currency |
| | | valueDate: |
| | | type: string |
| | | format: date |
| | | reference: |
| | | type: string |
| | | comment: |
| | | type: string |
| | | |
| | | HsOfficeCoopAssetsTransactionInsert: |
| | | type: object |
| | | properties: |
| | | membershipUuid: |
| | | type: string |
| | | format: uuid |
| | | nullable: false |
| | | transactionType: |
| | | $ref: '#/components/schemas/HsOfficeCoopAssetsTransactionType' |
| | | assetValue: |
| | | type: number |
| | | format: currency |
| | | valueDate: |
| | | type: string |
| | | format: date |
| | | reference: |
| | | type: string |
| | | minLength: 6 |
| | | maxLength: 48 |
| | | comment: |
| | | type: string |
| | | required: |
| | | - membershipUuid |
| | | - transactionType |
| | | - assetValue |
| | | - valueDate |
| | | - reference |
| | | additionalProperties: false |
New file |
| | |
| | | get: |
| | | summary: Returns a list of (optionally filtered) cooperative asset transactions. |
| | | description: Returns the list of (optionally filtered) cooperative asset transactions which are visible to the current user or any of it's assumed roles. |
| | | tags: |
| | | - hs-office-coopAssets |
| | | operationId: listCoopAssets |
| | | 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: 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-coopassets-schemas.yaml#/components/schemas/HsOfficeCoopAssetsTransaction' |
| | | "401": |
| | | $ref: './error-responses.yaml#/components/responses/Unauthorized' |
| | | "403": |
| | | $ref: './error-responses.yaml#/components/responses/Forbidden' |
| | | |
| | | post: |
| | | summary: Adds a new cooperative asset transaction. |
| | | tags: |
| | | - hs-office-coopAssets |
| | | operationId: addCoopAssetsTransaction |
| | | parameters: |
| | | - $ref: './auth.yaml#/components/parameters/currentUser' |
| | | - $ref: './auth.yaml#/components/parameters/assumedRoles' |
| | | requestBody: |
| | | description: A JSON object describing the new cooperative assets transaction. |
| | | required: true |
| | | content: |
| | | application/json: |
| | | schema: |
| | | $ref: '/hs-office-coopassets-schemas.yaml#/components/schemas/HsOfficeCoopAssetsTransactionInsert' |
| | | responses: |
| | | "201": |
| | | description: Created |
| | | content: |
| | | 'application/json': |
| | | schema: |
| | | $ref: './hs-office-coopassets-schemas.yaml#/components/schemas/HsOfficeCoopAssetsTransaction' |
| | | "401": |
| | | $ref: './error-responses.yaml#/components/responses/Unauthorized' |
| | | "403": |
| | | $ref: './error-responses.yaml#/components/responses/Forbidden' |
| | | "409": |
| | | $ref: './error-responses.yaml#/components/responses/Conflict' |
| | |
| | | |
| | | /api/hs/office/coopsharestransactions: |
| | | $ref: "./hs-office-coopshares.yaml" |
| | | |
| | | |
| | | # Coop Assets Transaction |
| | | |
| | | /api/hs/office/coopassetstransactions: |
| | | $ref: "./hs-office-coopassets.yaml" |
New file |
| | |
| | | --liquibase formatted sql |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopassets-MAIN-TABLE:1 endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | |
| | | CREATE TYPE HsOfficeCoopAssetsTransactionType AS ENUM ('ADJUSTMENT', |
| | | 'DEPOSIT', |
| | | 'DISBURSAL', |
| | | 'TRANSFER', |
| | | 'ADOPTION', |
| | | 'CLEARING', |
| | | 'LOSS'); |
| | | |
| | | CREATE CAST (character varying as HsOfficeCoopAssetsTransactionType) WITH INOUT AS IMPLICIT; |
| | | |
| | | create table if not exists hs_office_coopassetstransaction |
| | | ( |
| | | uuid uuid unique references RbacObject (uuid) initially deferred, |
| | | membershipUuid uuid not null references hs_office_membership(uuid), |
| | | transactionType HsOfficeCoopAssetsTransactionType not null, |
| | | valueDate date not null, |
| | | assetValue money, |
| | | reference varchar(48), |
| | | comment varchar(512) |
| | | ); |
| | | --// |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopassets-ASSET-VALUE-CONSTRAINT:1 endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | |
| | | create or replace function checkAssetsByMembershipUuid(forMembershipUuid UUID, newAssetValue money) |
| | | returns boolean |
| | | language plpgsql as $$ |
| | | declare |
| | | currentAssetValue money; |
| | | totalAssetValue money; |
| | | begin |
| | | select sum(cat.assetValue) |
| | | from hs_office_coopassetstransaction cat |
| | | where cat.membershipUuid = forMembershipUuid |
| | | into currentAssetValue; |
| | | totalAssetValue := currentAssetValue + newAssetValue; |
| | | if totalAssetValue::numeric < 0 then |
| | | raise exception '[400] coop assets transaction would result in a negative balance of assets'; |
| | | end if; |
| | | return true; |
| | | end; $$; |
| | | |
| | | alter table hs_office_coopassetstransaction |
| | | add constraint hs_office_coopassets_positive |
| | | check ( checkAssetsByMembershipUuid(membershipUuid, assetValue) ); |
| | | |
| | | --// |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopassets-MAIN-TABLE-JOURNAL:1 endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | |
| | | call create_journal('hs_office_coopassetstransaction'); |
| | | --// |
New file |
| | |
| | | ### hs_office_coopAssetsTransaction RBAC |
| | | |
| | | ```mermaid |
| | | flowchart TB |
| | | |
| | | subgraph hsOfficeMembership |
| | | direction TB |
| | | style hsOfficeMembership fill:#eee |
| | | |
| | | role:hsOfficeMembership.owner[membership.admin] |
| | | --> role:hsOfficeMembership.admin[membership.admin] |
| | | --> role:hsOfficeMembership.agent[membership.agent] |
| | | --> role:hsOfficeMembership.tenant[membership.tenant] |
| | | --> role:hsOfficeMembership.guest[membership.guest] |
| | | |
| | | role:hsOfficePartner.agent --> role:hsOfficeMembership.agent |
| | | end |
| | | |
| | | subgraph hsOfficeCoopAssetsTransaction |
| | | |
| | | role:hsOfficeMembership.admin |
| | | --> perm:hsOfficeCoopAssetsTransaction.create{{coopAssetsTx.create}} |
| | | |
| | | role:hsOfficeMembership.agent |
| | | --> perm:hsOfficeCoopAssetsTransaction.view{{coopAssetsTx.view}} |
| | | end |
| | | |
| | | |
| | | ``` |
New file |
| | |
| | | --liquibase formatted sql |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopAssetsTransaction-rbac-OBJECT:1 endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | call generateRelatedRbacObject('hs_office_coopAssetsTransaction'); |
| | | --// |
| | | |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopAssetsTransaction-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | call generateRbacRoleDescriptors('hsOfficeCoopAssetsTransaction', 'hs_office_coopAssetsTransaction'); |
| | | --// |
| | | |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopAssetsTransaction-rbac-ROLES-CREATION:1 endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | |
| | | /* |
| | | Creates and updates the permissions for coopAssetsTransaction entities. |
| | | */ |
| | | |
| | | create or replace function hsOfficeCoopAssetsTransactionRbacRolesTrigger() |
| | | returns trigger |
| | | language plpgsql |
| | | strict as $$ |
| | | declare |
| | | newHsOfficeMembership hs_office_membership; |
| | | begin |
| | | |
| | | select * from hs_office_membership as p where p.uuid = NEW.membershipUuid into newHsOfficeMembership; |
| | | |
| | | if TG_OP = 'INSERT' then |
| | | |
| | | -- Each coopAssetsTransaction entity belong exactly to one membership entity |
| | | -- and it makes little sense just to delegate coopAssetsTransaction roles. |
| | | -- Therefore, we do not create coopAssetsTransaction roles at all, |
| | | -- but instead just assign extra permissions to existing membership-roles. |
| | | |
| | | -- coopassetstransactions cannot be edited nor deleted, just created+viewed |
| | | call grantPermissionsToRole( |
| | | getRoleId(hsOfficeMembershipTenant(newHsOfficeMembership), 'fail'), |
| | | createPermissions(NEW.uuid, array ['view']) |
| | | ); |
| | | |
| | | else |
| | | raise exception 'invalid usage of TRIGGER'; |
| | | end if; |
| | | |
| | | return NEW; |
| | | end; $$; |
| | | |
| | | /* |
| | | An AFTER INSERT TRIGGER which creates the role structure for a new customer. |
| | | */ |
| | | create trigger createRbacRolesForHsOfficeCoopAssetsTransaction_Trigger |
| | | after insert |
| | | on hs_office_coopAssetsTransaction |
| | | for each row |
| | | execute procedure hsOfficeCoopAssetsTransactionRbacRolesTrigger(); |
| | | --// |
| | | |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopAssetsTransaction-rbac-IDENTITY-VIEW:1 endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | call generateRbacIdentityView('hs_office_coopAssetsTransaction', |
| | | idNameExpression => 'target.reference'); |
| | | --// |
| | | |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopAssetsTransaction-rbac-RESTRICTED-VIEW:1 endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | call generateRbacRestrictedView('hs_office_coopAssetsTransaction', orderby => 'target.reference'); |
| | | --// |
| | | |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopAssetsTransaction-rbac-NEW-CoopAssetsTransaction:1 endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | /* |
| | | Creates a global permission for new-coopAssetsTransaction and assigns it to the hostsharing admins role. |
| | | */ |
| | | do language plpgsql $$ |
| | | declare |
| | | addCustomerPermissions uuid[]; |
| | | globalObjectUuid uuid; |
| | | globalAdminRoleUuid uuid ; |
| | | begin |
| | | call defineContext('granting global new-coopAssetsTransaction permission to global admin role', null, null, null); |
| | | |
| | | globalAdminRoleUuid := findRoleId(globalAdmin()); |
| | | globalObjectUuid := (select uuid from global); |
| | | addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-coopassetstransaction']); |
| | | call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions); |
| | | end; |
| | | $$; |
| | | |
| | | /** |
| | | Used by the trigger to prevent the add-customer to current user respectively assumed roles. |
| | | */ |
| | | create or replace function addHsOfficeCoopAssetsTransactionNotAllowedForCurrentSubjects() |
| | | returns trigger |
| | | language PLPGSQL |
| | | as $$ |
| | | begin |
| | | raise exception '[403] new-coopassetstransaction not permitted for %', |
| | | array_to_string(currentSubjects(), ';', 'null'); |
| | | end; $$; |
| | | |
| | | /** |
| | | Checks if the user or assumed roles are allowed to create a new customer. |
| | | */ |
| | | create trigger hs_office_coopAssetsTransaction_insert_trigger |
| | | before insert |
| | | on hs_office_coopAssetsTransaction |
| | | for each row |
| | | when ( not hasAssumedRole() ) |
| | | execute procedure addHsOfficeCoopAssetsTransactionNotAllowedForCurrentSubjects(); |
| | | --// |
| | | |
New file |
| | |
| | | --liquibase formatted sql |
| | | |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopAssetsTransaction-TEST-DATA-GENERATOR:1 endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | |
| | | /* |
| | | Creates a single coopAssetsTransaction test record. |
| | | */ |
| | | create or replace procedure createHsOfficeCoopAssetsTransactionTestData(givenMembershipNumber numeric) |
| | | language plpgsql as $$ |
| | | declare |
| | | currentTask varchar; |
| | | membership hs_office_membership; |
| | | begin |
| | | currentTask = 'creating coopAssetsTransaction test-data ' || givenMembershipNumber; |
| | | execute format('set local hsadminng.currentTask to %L', currentTask); |
| | | |
| | | call defineContext(currentTask); |
| | | select m.uuid from hs_office_membership m where m.memberNumber = givenMembershipNumber into membership; |
| | | |
| | | raise notice 'creating test coopAssetsTransaction: %', givenMembershipNumber; |
| | | insert |
| | | into hs_office_coopassetstransaction(uuid, membershipuuid, transactiontype, valuedate, assetvalue, reference, comment) |
| | | values |
| | | (uuid_generate_v4(), membership.uuid, 'DEPOSIT', '2010-03-15', 320.00, 'ref '||givenMembershipNumber||'-1', 'initial deposit'), |
| | | (uuid_generate_v4(), membership.uuid, 'DISBURSAL', '2021-09-01', -128.00, 'ref '||givenMembershipNumber||'-2', 'partial disbursal'), |
| | | (uuid_generate_v4(), membership.uuid, 'ADJUSTMENT', '2022-10-20', 128.00, 'ref '||givenMembershipNumber||'-3', 'some adjustment'); |
| | | end; $$; |
| | | --// |
| | | |
| | | |
| | | -- ============================================================================ |
| | | --changeset hs-office-coopAssetsTransaction-TEST-DATA-GENERATION:1 –context=dev,tc endDelimiter:--// |
| | | -- ---------------------------------------------------------------------------- |
| | | |
| | | do language plpgsql $$ |
| | | begin |
| | | call createHsOfficeCoopAssetsTransactionTestData(10001); |
| | | call createHsOfficeCoopAssetsTransactionTestData(10002); |
| | | call createHsOfficeCoopAssetsTransactionTestData(10003); |
| | | end; |
| | | $$; |
| | |
| | | file: db/changelog/313-hs-office-coopshares-rbac.sql |
| | | - include: |
| | | file: db/changelog/318-hs-office-coopshares-test-data.sql |
| | | - include: |
| | | file: db/changelog/320-hs-office-coopassets.sql |
| | | - include: |
| | | file: db/changelog/323-hs-office-coopassets-rbac.sql |
| | | - include: |
| | | file: db/changelog/328-hs-office-coopassets-test-data.sql |
| | |
| | | "..hs.office.relationship", |
| | | "..hs.office.contact", |
| | | "..hs.office.sepamandate", |
| | | "..hs.office.coopassets", |
| | | "..hs.office.coopshares", |
| | | "..hs.office.membership", |
| | | "..errors", |
| | |
| | | public static final ArchRule hsOfficeMembershipPackageRule = classes() |
| | | .that().resideInAPackage("..hs.office.membership..") |
| | | .should().onlyBeAccessed().byClassesThat() |
| | | .resideInAnyPackage("..hs.office.membership..", "..hs.office.coopshares.."); |
| | | .resideInAnyPackage("..hs.office.membership..", "..hs.office.coopassets..", "..hs.office.coopshares.."); |
| | | |
| | | @ArchTest |
| | | @SuppressWarnings("unused") |
| | | public static final ArchRule hsOfficeCoopAssetsPackageRule = classes() |
| | | .that().resideInAPackage("..hs.office.coopassets..") |
| | | .should().onlyBeAccessed().byClassesThat() |
| | | .resideInAnyPackage("..hs.office.coopassets.."); |
| | | |
| | | @ArchTest |
| | | @SuppressWarnings("unused") |
New file |
| | |
| | | package net.hostsharing.hsadminng.hs.office.coopassets; |
| | | |
| | | import io.restassured.RestAssured; |
| | | import io.restassured.http.ContentType; |
| | | import net.hostsharing.hsadminng.HsadminNgApplication; |
| | | import net.hostsharing.hsadminng.context.Context; |
| | | import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository; |
| | | import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository; |
| | | import net.hostsharing.test.Accepts; |
| | | 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 HsOfficeCoopAssetsTransactionControllerAcceptanceTest { |
| | | |
| | | @LocalServerPort |
| | | private Integer port; |
| | | |
| | | @Autowired |
| | | Context context; |
| | | |
| | | @Autowired |
| | | HsOfficeMembershipRepository membershipRepo; |
| | | |
| | | @Autowired |
| | | JpaAttempt jpaAttempt; |
| | | |
| | | @Autowired |
| | | EntityManager em; |
| | | |
| | | @Nested |
| | | @Accepts({ "CoopAssetsTransaction:F(Find)" }) |
| | | class ListCoopAssetsTransactions { |
| | | |
| | | @Test |
| | | void globalAdmin_canViewAllCoopAssetsTransactions() { |
| | | |
| | | RestAssured // @formatter:off |
| | | .given() |
| | | .header("current-user", "superuser-alex@hostsharing.net") |
| | | .port(port) |
| | | .when() |
| | | .get("http://localhost/api/hs/office/coopassetstransactions") |
| | | .then().log().all().assertThat() |
| | | .statusCode(200) |
| | | .contentType("application/json") |
| | | .body("", hasSize(9)); // @formatter:on |
| | | } |
| | | |
| | | @Test |
| | | void globalAdmin_canFindCoopAssetsTransactionsByMemberNumber() { |
| | | |
| | | context.define("superuser-alex@hostsharing.net"); |
| | | final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002) |
| | | .get(0); |
| | | |
| | | RestAssured // @formatter:off |
| | | .given() |
| | | .header("current-user", "superuser-alex@hostsharing.net") |
| | | .port(port) |
| | | .when() |
| | | .get("http://localhost/api/hs/office/coopassetstransactions?membershipUuid="+givenMembership.getUuid()) |
| | | .then().log().all().assertThat() |
| | | .statusCode(200) |
| | | .contentType("application/json") |
| | | .body("", lenientlyEquals(""" |
| | | [ |
| | | { |
| | | "transactionType": "DEPOSIT", |
| | | "assetValue": 320.00, |
| | | "valueDate": "2010-03-15", |
| | | "reference": "ref 10002-1", |
| | | "comment": "initial deposit" |
| | | }, |
| | | { |
| | | "transactionType": "DISBURSAL", |
| | | "assetValue": -128.00, |
| | | "valueDate": "2021-09-01", |
| | | "reference": "ref 10002-2", |
| | | "comment": "partial disbursal" |
| | | }, |
| | | { |
| | | "transactionType": "ADJUSTMENT", |
| | | "assetValue": 128.00, |
| | | "valueDate": "2022-10-20", |
| | | "reference": "ref 10002-3", |
| | | "comment": "some adjustment" |
| | | } |
| | | ] |
| | | """)); // @formatter:on |
| | | } |
| | | |
| | | @Test |
| | | void globalAdmin_canFindCoopAssetsTransactionsByMemberNumberAndDateRange() { |
| | | |
| | | context.define("superuser-alex@hostsharing.net"); |
| | | final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002) |
| | | .get(0); |
| | | |
| | | RestAssured // @formatter:off |
| | | .given() |
| | | .header("current-user", "superuser-alex@hostsharing.net") |
| | | .port(port) |
| | | .when() |
| | | .get("http://localhost/api/hs/office/coopassetstransactions?membershipUuid=" |
| | | + givenMembership.getUuid() + "&fromValueDate=2020-01-01&toValueDate=2021-12-31") |
| | | .then().log().all().assertThat() |
| | | .statusCode(200) |
| | | .contentType("application/json") |
| | | .body("", lenientlyEquals(""" |
| | | [ |
| | | { |
| | | "transactionType": "DISBURSAL", |
| | | "assetValue": -128.00, |
| | | "valueDate": "2021-09-01", |
| | | "reference": "ref 10002-2", |
| | | "comment": "partial disbursal" |
| | | } |
| | | ] |
| | | """)); // @formatter:on |
| | | } |
| | | } |
| | | |
| | | @Nested |
| | | @Accepts({ "CoopAssetsTransaction:C(Create)" }) |
| | | class AddCoopAssetsTransaction { |
| | | |
| | | @Test |
| | | void globalAdmin_canAddCoopAssetsTransaction() { |
| | | |
| | | 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": "DEPOSIT", |
| | | "assetValue": 1024.00, |
| | | "valueDate": "2022-10-13", |
| | | "reference": "temp ref A", |
| | | "comment": "just some test coop assets transaction" |
| | | } |
| | | """.formatted(givenMembership.getUuid())) |
| | | .port(port) |
| | | .when() |
| | | .post("http://localhost/api/hs/office/coopassetstransactions") |
| | | .then().log().all().assertThat() |
| | | .statusCode(201) |
| | | .contentType(ContentType.JSON) |
| | | .body("uuid", isUuidValid()) |
| | | .body("", lenientlyEquals(""" |
| | | { |
| | | "transactionType": "DEPOSIT", |
| | | "assetValue": 1024.00, |
| | | "valueDate": "2022-10-13", |
| | | "reference": "temp ref A", |
| | | "comment": "just some test coop assets transaction" |
| | | } |
| | | """)) |
| | | .header("Location", startsWith("http://localhost")) |
| | | .extract().header("Location"); // @formatter:on |
| | | |
| | | // finally, the new coopAssetsTransaction can be accessed under the generated UUID |
| | | final var newUserUuid = UUID.fromString( |
| | | location.substring(location.lastIndexOf('/') + 1)); |
| | | assertThat(newUserUuid).isNotNull(); |
| | | } |
| | | |
| | | @Test |
| | | void globalAdmin_canNotCancelMoreAssetsThanCurrentlySubscribed() { |
| | | |
| | | 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": "DISBURSAL", |
| | | "assetValue": -10240.00, |
| | | "valueDate": "2022-10-13", |
| | | "reference": "temp ref X", |
| | | "comment": "just some test coop assets transaction" |
| | | } |
| | | """.formatted(givenMembership.getUuid())) |
| | | .port(port) |
| | | .when() |
| | | .post("http://localhost/api/hs/office/coopassetstransactions") |
| | | .then().log().all().assertThat() |
| | | .statusCode(400) |
| | | .contentType(ContentType.JSON) |
| | | .body("", lenientlyEquals(""" |
| | | { |
| | | "status": 400, |
| | | "error": "Bad Request", |
| | | "message": "ERROR: [400] coop assets transaction would result in a negative balance of assets" |
| | | } |
| | | """)); // @formatter:on |
| | | } |
| | | } |
| | | |
| | | @BeforeEach |
| | | @AfterEach |
| | | void cleanup() { |
| | | jpaAttempt.transacted(() -> { |
| | | context.define("superuser-alex@hostsharing.net", null); |
| | | // HsOfficeCoopAssetsTransactionEntity respectively hs_office_coopassetstransaction_rv |
| | | // cannot be deleted at all, but the underlying table record can be deleted. |
| | | em.createNativeQuery("delete from hs_office_coopassetstransaction where reference like 'temp %'") |
| | | .executeUpdate(); |
| | | }).assertSuccessful(); |
| | | } |
| | | } |
New file |
| | |
| | | package net.hostsharing.hsadminng.hs.office.coopassets; |
| | | |
| | | import net.hostsharing.hsadminng.context.Context; |
| | | import net.hostsharing.test.JsonBuilder; |
| | | import org.junit.jupiter.params.ParameterizedTest; |
| | | import org.junit.jupiter.params.provider.EnumSource; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; |
| | | import org.springframework.boot.test.mock.mockito.MockBean; |
| | | import org.springframework.http.MediaType; |
| | | import org.springframework.test.web.servlet.MockMvc; |
| | | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; |
| | | |
| | | import java.util.UUID; |
| | | import java.util.function.Function; |
| | | |
| | | import static net.hostsharing.test.JsonBuilder.jsonObject; |
| | | import static org.hamcrest.Matchers.is; |
| | | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; |
| | | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
| | | |
| | | @WebMvcTest(HsOfficeCoopAssetsTransactionController.class) |
| | | class HsOfficeCoopAssetsTransactionControllerRestTest { |
| | | |
| | | @Autowired |
| | | MockMvc mockMvc; |
| | | |
| | | @MockBean |
| | | Context contextMock; |
| | | |
| | | @MockBean |
| | | HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo; |
| | | |
| | | static final String VALID_INSERT_REQUEST_BODY = """ |
| | | { |
| | | "membershipUuid": "%s", |
| | | "transactionType": "DEPOSIT", |
| | | "assetValue": 128.00, |
| | | "valueDate": "2022-10-13", |
| | | "reference": "valid reference", |
| | | "comment": "valid comment" |
| | | } |
| | | """.formatted(UUID.randomUUID()); |
| | | |
| | | enum BadRequestTestCases { |
| | | MEMBERSHIP_UUID_MISSING( |
| | | requestBody -> requestBody.without("membershipUuid"), |
| | | "[membershipUuid must not be null but is \"null\"]"), |
| | | |
| | | TRANSACTION_TYPE_MISSING( |
| | | requestBody -> requestBody.without("transactionType"), |
| | | "[transactionType must not be null but is \"null\"]"), |
| | | |
| | | VALUE_DATE_MISSING( |
| | | requestBody -> requestBody.without("valueDate"), |
| | | "[valueDate must not be null but is \"null\"]"), |
| | | |
| | | ASSETS_VALUE_FOR_DEPOSIT_MUST_BE_POSITIVE( |
| | | requestBody -> requestBody |
| | | .with("transactionType", "DEPOSIT") |
| | | .with("assetValue", -64.00), |
| | | "[for DEPOSIT, assetValue must be positive but is \"-64.00\"]"), |
| | | |
| | | //TODO: other transaction types |
| | | |
| | | ASSETS_VALUE_FOR_DISBURSAL_MUST_BE_NEGATIVE( |
| | | requestBody -> requestBody |
| | | .with("transactionType", "DISBURSAL") |
| | | .with("assetValue", 64.00), |
| | | "[for DISBURSAL, assetValue must be negative but is \"64.00\"]"), |
| | | |
| | | //TODO: other transaction types |
| | | |
| | | ASSETS_VALUE_MUST_NOT_BE_NULL( |
| | | requestBody -> requestBody |
| | | .with("transactionType", "ADJUSTMENT") |
| | | .with("assetValue", 0.00), |
| | | "[assetValue must not be 0 but is \"0.00\"]"), |
| | | |
| | | REFERENCE_MISSING( |
| | | requestBody -> requestBody.without("reference"), |
| | | "[reference must not be null but is \"null\"]"), |
| | | |
| | | REFERENCE_TOO_SHORT( |
| | | requestBody -> requestBody.with("reference", "12345"), |
| | | "[reference size must be between 6 and 48 but is \"12345\"]"), |
| | | |
| | | REFERENCE_TOO_LONG( |
| | | requestBody -> requestBody.with("reference", "0123456789012345678901234567890123456789012345678"), |
| | | "[reference size must be between 6 and 48 but is \"0123456789012345678901234567890123456789012345678\"]"); |
| | | |
| | | private final Function<JsonBuilder, JsonBuilder> givenBodyTransformation; |
| | | private final String expectedErrorMessage; |
| | | |
| | | BadRequestTestCases( |
| | | final Function<JsonBuilder, JsonBuilder> givenBodyTransformation, |
| | | final String expectedErrorMessage) { |
| | | this.givenBodyTransformation = givenBodyTransformation; |
| | | this.expectedErrorMessage = expectedErrorMessage; |
| | | } |
| | | |
| | | String givenRequestBody() { |
| | | return givenBodyTransformation.apply(jsonObject(VALID_INSERT_REQUEST_BODY)).toString(); |
| | | } |
| | | } |
| | | |
| | | @ParameterizedTest |
| | | @EnumSource(BadRequestTestCases.class) |
| | | void respondWithBadRequest(final BadRequestTestCases testCase) throws Exception { |
| | | |
| | | // when |
| | | mockMvc.perform(MockMvcRequestBuilders |
| | | .post("/api/hs/office/coopassetstransactions") |
| | | .header("current-user", "superuser-alex@hostsharing.net") |
| | | .contentType(MediaType.APPLICATION_JSON) |
| | | .content(testCase.givenRequestBody()) |
| | | .accept(MediaType.APPLICATION_JSON)) |
| | | |
| | | // then |
| | | .andExpect(status().is4xxClientError()) |
| | | .andExpect(jsonPath("status", is(400))) |
| | | .andExpect(jsonPath("error", is("Bad Request"))) |
| | | .andExpect(jsonPath("message", is(testCase.expectedErrorMessage))); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package net.hostsharing.hsadminng.hs.office.coopassets; |
| | | |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | |
| | | import static net.hostsharing.hsadminng.hs.office.membership.TestHsMembership.testMembership; |
| | | import static org.assertj.core.api.Assertions.assertThat; |
| | | |
| | | class HsOfficeCoopAssetsTransactionEntityTest { |
| | | |
| | | final HsOfficeCoopAssetsTransactionEntity givenCoopAssetTransaction = HsOfficeCoopAssetsTransactionEntity.builder() |
| | | .membership(testMembership) |
| | | .reference("some-ref") |
| | | .valueDate(LocalDate.parse("2020-01-01")) |
| | | .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT) |
| | | .assetValue(new BigDecimal("128.00")) |
| | | .build(); |
| | | |
| | | @Test |
| | | void toStringContainsAlmostAllPropertiesAccount() { |
| | | final var result = givenCoopAssetTransaction.toString(); |
| | | |
| | | assertThat(result).isEqualTo("CoopAssetsTransaction(300001, 2020-01-01, DEPOSIT, 128.00, some-ref)"); |
| | | } |
| | | |
| | | @Test |
| | | void toShortStringContainsOnlyMemberNumberAndSharesCountOnly() { |
| | | final var result = givenCoopAssetTransaction.toShortString(); |
| | | |
| | | assertThat(result).isEqualTo("300001+128.00"); |
| | | } |
| | | } |
New file |
| | |
| | | package net.hostsharing.hsadminng.hs.office.coopassets; |
| | | |
| | | import net.hostsharing.hsadminng.context.Context; |
| | | import net.hostsharing.hsadminng.context.ContextBasedTest; |
| | | import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository; |
| | | import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; |
| | | import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; |
| | | import net.hostsharing.test.Array; |
| | | 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.autoconfigure.orm.jpa.DataJpaTest; |
| | | import org.springframework.boot.test.mock.mockito.MockBean; |
| | | import org.springframework.context.annotation.ComponentScan; |
| | | import org.springframework.test.annotation.DirtiesContext; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import javax.persistence.EntityManager; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.UUID; |
| | | |
| | | import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantDisplaysOf; |
| | | import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf; |
| | | import static net.hostsharing.test.JpaAttempt.attempt; |
| | | import static org.assertj.core.api.Assertions.assertThat; |
| | | |
| | | @DataJpaTest |
| | | @ComponentScan(basePackageClasses = { HsOfficeCoopAssetsTransactionRepository.class, Context.class, JpaAttempt.class }) |
| | | @DirtiesContext |
| | | class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBasedTest { |
| | | |
| | | @Autowired |
| | | HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo; |
| | | |
| | | @Autowired |
| | | HsOfficeMembershipRepository membershipRepo; |
| | | |
| | | @Autowired |
| | | RawRbacRoleRepository rawRoleRepo; |
| | | |
| | | @Autowired |
| | | RawRbacGrantRepository rawGrantRepo; |
| | | |
| | | @Autowired |
| | | EntityManager em; |
| | | |
| | | @Autowired |
| | | JpaAttempt jpaAttempt; |
| | | |
| | | @MockBean |
| | | HttpServletRequest request; |
| | | |
| | | @Nested |
| | | class CreateCoopAssetsTransaction { |
| | | |
| | | @Test |
| | | public void globalAdmin_canCreateNewCoopAssetTransaction() { |
| | | // given |
| | | context("superuser-alex@hostsharing.net"); |
| | | final var count = coopAssetsTransactionRepo.count(); |
| | | final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10001) |
| | | .get(0); |
| | | |
| | | // when |
| | | final var result = attempt(em, () -> { |
| | | final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder() |
| | | .uuid(UUID.randomUUID()) |
| | | .membership(givenMembership) |
| | | .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT) |
| | | .assetValue(new BigDecimal("128.00")) |
| | | .valueDate(LocalDate.parse("2022-10-18")) |
| | | .reference("temp ref A") |
| | | .build(); |
| | | return coopAssetsTransactionRepo.save(newCoopAssetsTransaction); |
| | | }); |
| | | |
| | | // then |
| | | result.assertSuccessful(); |
| | | assertThat(result.returnedValue()).isNotNull().extracting(HsOfficeCoopAssetsTransactionEntity::getUuid).isNotNull(); |
| | | assertThatCoopAssetsTransactionIsPersisted(result.returnedValue()); |
| | | assertThat(coopAssetsTransactionRepo.count()).isEqualTo(count + 1); |
| | | } |
| | | |
| | | @Test |
| | | public void createsAndGrantsRoles() { |
| | | // given |
| | | context("superuser-alex@hostsharing.net"); |
| | | final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll()); |
| | | final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()).stream() |
| | | .map(s -> s.replace("FirstGmbH-firstcontact", "...")) |
| | | .map(s -> s.replace("hs_office_", "")) |
| | | .toList(); |
| | | |
| | | // when |
| | | attempt(em, () -> { |
| | | final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber( |
| | | null, |
| | | 10001).get(0); |
| | | final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder() |
| | | .uuid(UUID.randomUUID()) |
| | | .membership(givenMembership) |
| | | .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT) |
| | | .assetValue(new BigDecimal("128.00")) |
| | | .valueDate(LocalDate.parse("2022-10-18")) |
| | | .reference("temp ref B") |
| | | .build(); |
| | | return coopAssetsTransactionRepo.save(newCoopAssetsTransaction); |
| | | }); |
| | | |
| | | // then |
| | | final var all = rawRoleRepo.findAll(); |
| | | assertThat(roleNamesOf(all)).containsExactlyInAnyOrder(Array.from(initialRoleNames)); // no new roles created |
| | | assertThat(grantDisplaysOf(rawGrantRepo.findAll())) |
| | | .map(s -> s.replace("FirstGmbH-firstcontact", "...")) |
| | | .map(s -> s.replace("hs_office_", "")) |
| | | .containsExactlyInAnyOrder(Array.fromFormatted( |
| | | initialGrantNames, |
| | | "{ grant perm view on coopassetstransaction#temprefB to role membership#10001....tenant by system and assume }", |
| | | null)); |
| | | } |
| | | |
| | | private void assertThatCoopAssetsTransactionIsPersisted(final HsOfficeCoopAssetsTransactionEntity saved) { |
| | | final var found = coopAssetsTransactionRepo.findByUuid(saved.getUuid()); |
| | | assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved); |
| | | } |
| | | } |
| | | |
| | | @Nested |
| | | class FindAllCoopAssetsTransactions { |
| | | |
| | | @Test |
| | | public void globalAdmin_anViewAllCoopAssetsTransactions() { |
| | | // given |
| | | context("superuser-alex@hostsharing.net"); |
| | | |
| | | // when |
| | | final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( |
| | | null, |
| | | null, |
| | | null); |
| | | |
| | | // then |
| | | allTheseCoopAssetsTransactionsAreReturned( |
| | | result, |
| | | "CoopAssetsTransaction(10001, 2010-03-15, DEPOSIT, 320.00, ref 10001-1)", |
| | | "CoopAssetsTransaction(10001, 2021-09-01, DISBURSAL, -128.00, ref 10001-2)", |
| | | "CoopAssetsTransaction(10001, 2022-10-20, ADJUSTMENT, 128.00, ref 10001-3)", |
| | | |
| | | "CoopAssetsTransaction(10002, 2010-03-15, DEPOSIT, 320.00, ref 10002-1)", |
| | | "CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2)", |
| | | "CoopAssetsTransaction(10002, 2022-10-20, ADJUSTMENT, 128.00, ref 10002-3)", |
| | | |
| | | "CoopAssetsTransaction(10003, 2010-03-15, DEPOSIT, 320.00, ref 10003-1)", |
| | | "CoopAssetsTransaction(10003, 2021-09-01, DISBURSAL, -128.00, ref 10003-2)", |
| | | "CoopAssetsTransaction(10003, 2022-10-20, ADJUSTMENT, 128.00, ref 10003-3)"); |
| | | } |
| | | |
| | | @Test |
| | | public void globalAdmin_canViewCoopAssetsTransactions_filteredByMembershipUuid() { |
| | | // given |
| | | context("superuser-alex@hostsharing.net"); |
| | | final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002) |
| | | .get(0); |
| | | |
| | | // when |
| | | final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( |
| | | givenMembership.getUuid(), |
| | | null, |
| | | null); |
| | | |
| | | // then |
| | | allTheseCoopAssetsTransactionsAreReturned( |
| | | result, |
| | | "CoopAssetsTransaction(10002, 2010-03-15, DEPOSIT, 320.00, ref 10002-1)", |
| | | "CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2)", |
| | | "CoopAssetsTransaction(10002, 2022-10-20, ADJUSTMENT, 128.00, ref 10002-3)"); |
| | | } |
| | | |
| | | @Test |
| | | public void globalAdmin_canViewCoopAssetsTransactions_filteredByMembershipUuidAndValueDateRange() { |
| | | // given |
| | | context("superuser-alex@hostsharing.net"); |
| | | final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002) |
| | | .get(0); |
| | | |
| | | // when |
| | | final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( |
| | | givenMembership.getUuid(), |
| | | LocalDate.parse("2021-09-01"), |
| | | LocalDate.parse("2021-09-01")); |
| | | |
| | | // then |
| | | allTheseCoopAssetsTransactionsAreReturned( |
| | | result, |
| | | "CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2)"); |
| | | } |
| | | |
| | | @Test |
| | | public void normalUser_canViewOnlyRelatedCoopAssetsTransactions() { |
| | | // given: |
| | | context("superuser-alex@hostsharing.net", "hs_office_partner#FirstGmbH-firstcontact.admin"); |
| | | // "hs_office_person#FirstGmbH.admin", |
| | | |
| | | // when: |
| | | final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( |
| | | null, |
| | | null, |
| | | null); |
| | | |
| | | // then: |
| | | exactlyTheseCoopAssetsTransactionsAreReturned( |
| | | result, |
| | | "CoopAssetsTransaction(10001, 2010-03-15, DEPOSIT, 320.00, ref 10001-1)", |
| | | "CoopAssetsTransaction(10001, 2021-09-01, DISBURSAL, -128.00, ref 10001-2)", |
| | | "CoopAssetsTransaction(10001, 2022-10-20, ADJUSTMENT, 128.00, ref 10001-3)"); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | public void auditJournalLogIsAvailable() { |
| | | // given |
| | | final var query = em.createNativeQuery(""" |
| | | select c.currenttask, j.targettable, j.targetop |
| | | from tx_journal j |
| | | join tx_context c on j.contextId = c.contextId |
| | | where targettable = 'hs_office_coopassetstransaction'; |
| | | """); |
| | | |
| | | // when |
| | | @SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList(); |
| | | |
| | | // then |
| | | assertThat(customerLogEntries).map(Arrays::toString).contains( |
| | | "[creating coopAssetsTransaction test-data 10001, hs_office_coopassetstransaction, INSERT]", |
| | | "[creating coopAssetsTransaction test-data 10002, hs_office_coopassetstransaction, INSERT]"); |
| | | } |
| | | |
| | | @BeforeEach |
| | | @AfterEach |
| | | void cleanup() { |
| | | jpaAttempt.transacted(() -> { |
| | | context("superuser-alex@hostsharing.net", null); |
| | | em.createQuery("DELETE FROM HsOfficeCoopAssetsTransactionEntity WHERE reference like 'temp ref%'"); |
| | | }); |
| | | } |
| | | |
| | | void exactlyTheseCoopAssetsTransactionsAreReturned( |
| | | final List<HsOfficeCoopAssetsTransactionEntity> actualResult, |
| | | final String... coopAssetsTransactionNames) { |
| | | assertThat(actualResult) |
| | | .extracting(coopAssetsTransactionEntity -> coopAssetsTransactionEntity.toString()) |
| | | .containsExactlyInAnyOrder(coopAssetsTransactionNames); |
| | | } |
| | | |
| | | void allTheseCoopAssetsTransactionsAreReturned( |
| | | final List<HsOfficeCoopAssetsTransactionEntity> actualResult, |
| | | final String... coopAssetsTransactionNames) { |
| | | assertThat(actualResult) |
| | | .extracting(coopAssetsTransactionEntity -> coopAssetsTransactionEntity.toString()) |
| | | .contains(coopAssetsTransactionNames); |
| | | } |
| | | } |