Compare commits

..

5 Commits

Author SHA1 Message Date
Michael Hoennig
8983cfc325 Merge remote-tracking branch 'origin/master' into introduce-booking-module 2024-04-13 11:15:06 +02:00
Michael Hoennig
029a35f928 cleanup 2024-04-13 11:13:57 +02:00
Michael Hoennig
ef8bf2cfaf fix acceptance tests 2024-04-13 10:30:08 +02:00
b0a28200f9 coop-shares-transaction-reverse-entry (#40)
Co-authored-by: Marc O. Sandlus <marc.o.sandlus@hostsharing.net>
Reviewed-on: #40
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-12 11:29:26 +02:00
216886e5f4 add-reverse-mapping-for-assetsharestx-adjustment (#38)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #38
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-11 10:08:45 +02:00
28 changed files with 440 additions and 180 deletions

View File

@ -1,11 +1,11 @@
package net.hostsharing.hsadminng.hs.booking.item; package net.hostsharing.hsadminng.hs.booking.item;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingItemsApi; import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingItemsApi;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemInsertResource; import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemInsertResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource; import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemResource; import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemResource;
import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.hsadminng.mapper.Mapper;
import net.hostsharing.hsadminng.context.Context;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -76,10 +76,10 @@ public class HsBookingItemController implements HsBookingItemsApi {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var result = bookingItemRepo.findByUuid(bookingItemUuid); final var result = bookingItemRepo.findByUuid(bookingItemUuid);
if (result.isEmpty()) { return result
return ResponseEntity.notFound().build(); .map(bookingItemEntity -> ResponseEntity.ok(
} mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)))
return ResponseEntity.ok(mapper.map(result.get(), HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)); .orElseGet(() -> ResponseEntity.notFound().build());
} }
@Override @Override
@ -91,11 +91,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var result = bookingItemRepo.deleteByUuid(bookingItemUuid); final var result = bookingItemRepo.deleteByUuid(bookingItemUuid);
if (result == 0) { return result == 0
return ResponseEntity.notFound().build(); ? ResponseEntity.notFound().build()
} : ResponseEntity.noContent().build();
return ResponseEntity.noContent().build();
} }
@Override @Override
@ -122,7 +120,6 @@ public class HsBookingItemController implements HsBookingItemsApi {
if (entity.getValidity().hasUpperBound()) { if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1)); resource.setValidTo(entity.getValidity().upper().minusDays(1));
} }
resource.getDebitor().setDebitorNumber(entity.getDebitor().getDebitorNumber());
}; };
final BiConsumer<HsBookingItemInsertResource, HsBookingItemEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsBookingItemInsertResource, HsBookingItemEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {

View File

@ -118,6 +118,7 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
public static RbacView rbac() { public static RbacView rbac() {
return rbacViewFor("bookingItem", HsBookingItemEntity.class) return rbacViewFor("bookingItem", HsBookingItemEntity.class)
.withIdentityView(SQL.projection("caption")) // FIXME: use memberNumber:caption .withIdentityView(SQL.projection("caption")) // FIXME: use memberNumber:caption
.withRestrictedViewOrderBy(SQL.expression("validity"))
.withUpdatableColumns("version", "validity", "resources") .withUpdatableColumns("version", "validity", "resources")
.importEntityAlias("debitor", HsOfficeDebitorEntity.class, .importEntityAlias("debitor", HsOfficeDebitorEntity.class,

View File

@ -74,11 +74,11 @@ public class HsOfficeBankAccountController implements HsOfficeBankAccountsApi {
public ResponseEntity<HsOfficeBankAccountResource> getBankAccountByUuid( public ResponseEntity<HsOfficeBankAccountResource> getBankAccountByUuid(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
final UUID BankAccountUuid) { final UUID bankAccountUuid) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var result = bankAccountRepo.findByUuid(BankAccountUuid); final var result = bankAccountRepo.findByUuid(bankAccountUuid);
if (result.isEmpty()) { if (result.isEmpty()) {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }

View File

@ -51,6 +51,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, RbacO
.withProp(HsOfficeCoopAssetsTransactionEntity::getReference) .withProp(HsOfficeCoopAssetsTransactionEntity::getReference)
.withProp(HsOfficeCoopAssetsTransactionEntity::getComment) .withProp(HsOfficeCoopAssetsTransactionEntity::getComment)
.withProp(at -> ofNullable(at.getAdjustedAssetTx()).map(HsOfficeCoopAssetsTransactionEntity::toShortString).orElse(null)) .withProp(at -> ofNullable(at.getAdjustedAssetTx()).map(HsOfficeCoopAssetsTransactionEntity::toShortString).orElse(null))
.withProp(at -> ofNullable(at.getAdjustmentAssetTx()).map(HsOfficeCoopAssetsTransactionEntity::toShortString).orElse(null))
.quotedValues(false); .quotedValues(false);
@Id @Id
@ -101,8 +102,11 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, RbacO
@JoinColumn(name = "adjustedassettxuuid") @JoinColumn(name = "adjustedassettxuuid")
private HsOfficeCoopAssetsTransactionEntity adjustedAssetTx; private HsOfficeCoopAssetsTransactionEntity adjustedAssetTx;
@OneToOne(mappedBy = "adjustedAssetTx")
private HsOfficeCoopAssetsTransactionEntity adjustmentAssetTx;
public String getTaggedMemberNumber() { public String getTaggedMemberNumber() {
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-?????"); return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-???????");
} }
@Override @Override
@ -112,7 +116,10 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, RbacO
@Override @Override
public String toShortString() { public String toShortString() {
return "%s:%+1.2f".formatted(getTaggedMemberNumber(), Optional.ofNullable(assetValue).orElse(BigDecimal.ZERO)); return "%s:%.3s:%+1.2f".formatted(
getTaggedMemberNumber(),
transactionType,
ofNullable(assetValue).orElse(BigDecimal.ZERO));
} }
public static RbacView rbac() { public static RbacView rbac() {

View File

@ -1,7 +1,10 @@
package net.hostsharing.hsadminng.hs.office.coopshares; package net.hostsharing.hsadminng.hs.office.coopshares;
import jakarta.persistence.EntityNotFoundException;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopAssetsTransactionInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.hsadminng.mapper.Mapper;
@ -18,6 +21,7 @@ import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer;
import static java.lang.String.join; import static java.lang.String.join;
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.CANCELLATION; import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.CANCELLATION;
@ -64,7 +68,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
validate(requestBody); validate(requestBody);
final var entityToSave = mapper.map(requestBody, HsOfficeCoopSharesTransactionEntity.class); final var entityToSave = mapper.map(requestBody, HsOfficeCoopSharesTransactionEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = coopSharesTransactionRepo.save(entityToSave); final var saved = coopSharesTransactionRepo.save(entityToSave);
@ -131,4 +135,10 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
} }
} }
final BiConsumer<HsOfficeCoopSharesTransactionInsertResource, HsOfficeCoopSharesTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
if ( resource.getAdjustedShareTxUuid() != null ) {
entity.setAdjustedShareTx(coopSharesTransactionRepo.findByUuid(resource.getAdjustedShareTxUuid())
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] adjustedShareTxUuid %s not found".formatted(resource.getAdjustedShareTxUuid()))));
}
};
} }

View File

@ -6,6 +6,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
@ -41,13 +42,15 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacObject { public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacObject {
private static Stringify<HsOfficeCoopSharesTransactionEntity> stringify = stringify(HsOfficeCoopSharesTransactionEntity.class) private static Stringify<HsOfficeCoopSharesTransactionEntity> stringify = stringify(HsOfficeCoopSharesTransactionEntity.class)
.withProp(HsOfficeCoopSharesTransactionEntity::getMemberNumberTagged) .withIdProp(HsOfficeCoopSharesTransactionEntity::getMemberNumberTagged)
.withProp(HsOfficeCoopSharesTransactionEntity::getValueDate) .withProp(HsOfficeCoopSharesTransactionEntity::getValueDate)
.withProp(HsOfficeCoopSharesTransactionEntity::getTransactionType) .withProp(HsOfficeCoopSharesTransactionEntity::getTransactionType)
.withProp(HsOfficeCoopSharesTransactionEntity::getShareCount) .withProp(HsOfficeCoopSharesTransactionEntity::getShareCount)
.withProp(HsOfficeCoopSharesTransactionEntity::getReference) .withProp(HsOfficeCoopSharesTransactionEntity::getReference)
.withProp(HsOfficeCoopSharesTransactionEntity::getComment) .withProp(HsOfficeCoopSharesTransactionEntity::getComment)
.quotedValues(false); .withProp(at -> ofNullable(at.getAdjustedShareTx()).map(HsOfficeCoopSharesTransactionEntity::toShortString).orElse(null))
.withProp(at -> ofNullable(at.getAdjustmentShareTx()).map(HsOfficeCoopSharesTransactionEntity::toShortString).orElse(null))
.quotedValues(false);
@Id @Id
@GeneratedValue @GeneratedValue
@ -89,6 +92,16 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacO
@Column(name = "comment") @Column(name = "comment")
private String comment; private String comment;
/**
* Optionally, the UUID of the corresponding transaction for an adjustment transaction.
*/
@OneToOne
@JoinColumn(name = "adjustedsharetxuuid")
private HsOfficeCoopSharesTransactionEntity adjustedShareTx;
@OneToOne(mappedBy = "adjustedShareTx")
private HsOfficeCoopSharesTransactionEntity adjustmentShareTx;
@Override @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);
@ -100,7 +113,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacO
@Override @Override
public String toShortString() { public String toShortString() {
return "%s%+d".formatted(getMemberNumberTagged(), shareCount); return "%s:%.3s:%+d".formatted(getMemberNumberTagged(), transactionType, shareCount);
} }
public static RbacView rbac() { public static RbacView rbac() {

View File

@ -14,5 +14,5 @@ map:
- type: string:format => java.lang.String - type: string:format => java.lang.String
paths: paths:
/api/hs/booking/items/{itemUUID}: /api/hs/booking/items/{bookingItemUuid}:
null: org.openapitools.jackson.nullable.JsonNullable null: org.openapitools.jackson.nullable.JsonNullable

View File

@ -9,8 +9,6 @@ components:
uuid: uuid:
type: string type: string
format: uuid format: uuid
debitor:
$ref: '../hs-office/hs-office-debitor-schemas.yaml#/components/schemas/HsOfficeDebitor'
caption: caption:
type: string type: string
validFrom: validFrom:
@ -44,7 +42,6 @@ components:
nullable: true nullable: true
resources: resources:
$ref: '#/components/schemas/ArbitraryBookingResourcesJson' $ref: '#/components/schemas/ArbitraryBookingResourcesJson'
additionalProperties: false
HsBookingItemInsert: HsBookingItemInsert:
type: object type: object

View File

@ -6,7 +6,7 @@ get:
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: './auth.yaml#/components/parameters/assumedRoles'
- name: bookingItemUUID - name: bookingItemUuid
in: path in: path
required: true required: true
schema: schema:
@ -34,7 +34,7 @@ patch:
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: './auth.yaml#/components/parameters/assumedRoles'
- name: bookingItemUUID - name: bookingItemUuid
in: path in: path
required: true required: true
schema: schema:
@ -65,7 +65,7 @@ delete:
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: './auth.yaml#/components/parameters/assumedRoles'
- name: bookingItemUUID - name: bookingItemUuid
in: path in: path
required: true required: true
schema: schema:

View File

@ -1,6 +1,6 @@
get: get:
summary: Returns a list of (optionally filtered) booking items. summary: Returns a list of all booking items for a specified debitor.
description: Returns the list of (optionally filtered) booking items which are visible to the current user or any of it's assumed roles. description: Returns the list of all booking items for a specified debitor which are visible to the current user or any of it's assumed roles.
tags: tags:
- hs-booking-items - hs-booking-items
operationId: listBookingItemsByDebitorUuid operationId: listBookingItemsByDebitorUuid
@ -9,7 +9,7 @@ get:
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: './auth.yaml#/components/parameters/assumedRoles'
- name: debitorUuid - name: debitorUuid
in: query in: query
required: false required: true
schema: schema:
type: string type: string
format: uuid format: uuid

View File

@ -13,5 +13,5 @@ paths:
/api/hs/booking/items: /api/hs/booking/items:
$ref: "./hs-booking-items.yaml" $ref: "./hs-booking-items.yaml"
/api/hs/booking/items/{itemUUID}: /api/hs/booking/items/{bookingItemUuid}:
$ref: "./hs-booking-items-with-uuid.yaml" $ref: "./hs-booking-items-with-uuid.yaml"

View File

@ -33,12 +33,14 @@ components:
comment: comment:
type: string type: string
adjustedAssetTx: adjustedAssetTx:
$ref: '#/components/schemas/HsOfficeAdjustedCoopAssetsTransaction' $ref: '#/components/schemas/HsOfficeReferencedCoopAssetsTransaction'
adjustmentAssetTx:
$ref: '#/components/schemas/HsOfficeReferencedCoopAssetsTransaction'
HsOfficeAdjustedCoopAssetsTransaction: HsOfficeReferencedCoopAssetsTransaction:
description: description:
Similar to `HsOfficeCoopAssetsTransaction` but without the `reverseEntry`, Similar to `HsOfficeCoopAssetsTransaction` but without the self-referencing properties
otherwise the JSON would be recursive. (`adjustedAssetTx` and `adjustmentAssetTx`), to avoid recursive JSON.
type: object type: object
properties: properties:
uuid: uuid:

View File

@ -27,6 +27,31 @@ components:
type: string type: string
comment: comment:
type: string type: string
adjustedShareTx:
$ref: '#/components/schemas/HsOfficeReferencedCoopSharesTransaction'
adjustmentShareTx:
$ref: '#/components/schemas/HsOfficeReferencedCoopSharesTransaction'
HsOfficeReferencedCoopSharesTransaction:
description:
Similar to `HsOfficeCoopSharesTransaction` but without the self-referencing properties
(`adjustedShareTx` and `adjustmentShareTx`), to avoid recursive JSON.
type: object
properties:
uuid:
type: string
format: uuid
transactionType:
$ref: '#/components/schemas/HsOfficeCoopSharesTransactionType'
shareCount:
type: integer
valueDate:
type: string
format: date
reference:
type: string
comment:
type: string
HsOfficeCoopSharesTransactionInsert: HsOfficeCoopSharesTransactionInsert:
type: object type: object
@ -48,6 +73,9 @@ components:
maxLength: 48 maxLength: 48
comment: comment:
type: string type: string
adjustedShareTxUuid:
type: string
format: uuid
required: required:
- membershipUuid - membershipUuid
- transactionType - transactionType

View File

@ -15,12 +15,23 @@ create table if not exists hs_office_coopsharestransaction
membershipUuid uuid not null references hs_office_membership(uuid), membershipUuid uuid not null references hs_office_membership(uuid),
transactionType HsOfficeCoopSharesTransactionType not null, transactionType HsOfficeCoopSharesTransactionType not null,
valueDate date not null, valueDate date not null,
shareCount integer, shareCount integer not null,
reference varchar(48), reference varchar(48) not null,
adjustedShareTxUuid uuid unique REFERENCES hs_office_coopsharestransaction(uuid) DEFERRABLE INITIALLY DEFERRED,
comment varchar(512) comment varchar(512)
); );
--// --//
-- ============================================================================
--changeset hs-office-coopshares-BUSINESS-RULES:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
alter table hs_office_coopsharestransaction
add constraint hs_office_coopsharestransaction_reverse_entry_missing
check ( transactionType = 'ADJUSTMENT' and adjustedShareTxUuid is not null
or transactionType <> 'ADJUSTMENT' and adjustedShareTxUuid is null);
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-coopshares-SHARE-COUNT-CONSTRAINT:1 endDelimiter:--// --changeset hs-office-coopshares-SHARE-COUNT-CONSTRAINT:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------

View File

@ -14,11 +14,13 @@ create or replace procedure createHsOfficeCoopSharesTransactionTestData(
) )
language plpgsql as $$ language plpgsql as $$
declare declare
currentTask varchar; currentTask varchar;
membership hs_office_membership; membership hs_office_membership;
subscriptionEntryUuid uuid;
begin begin
currentTask = 'creating coopSharesTransaction test-data ' || givenPartnerNumber::text || givenMemberNumberSuffix; currentTask = 'creating coopSharesTransaction test-data ' || givenPartnerNumber::text || givenMemberNumberSuffix;
execute format('set local hsadminng.currentTask to %L', currentTask); execute format('set local hsadminng.currentTask to %L', currentTask);
SET CONSTRAINTS ALL DEFERRED;
call defineContext(currentTask); call defineContext(currentTask);
select m.uuid select m.uuid
@ -29,12 +31,14 @@ begin
into membership; into membership;
raise notice 'creating test coopSharesTransaction: %', givenPartnerNumber::text || givenMemberNumberSuffix; raise notice 'creating test coopSharesTransaction: %', givenPartnerNumber::text || givenMemberNumberSuffix;
subscriptionEntryUuid := uuid_generate_v4();
insert insert
into hs_office_coopsharestransaction(uuid, membershipuuid, transactiontype, valuedate, sharecount, reference, comment) into hs_office_coopsharestransaction(uuid, membershipuuid, transactiontype, valuedate, sharecount, reference, comment, adjustedShareTxUuid)
values values
(uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2010-03-15', 4, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-1', 'initial subscription'), (uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2010-03-15', 4, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-1', 'initial subscription', null),
(uuid_generate_v4(), membership.uuid, 'CANCELLATION', '2021-09-01', -2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-2', 'cancelling some'), (uuid_generate_v4(), membership.uuid, 'CANCELLATION', '2021-09-01', -2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-2', 'cancelling some', null),
(uuid_generate_v4(), membership.uuid, 'ADJUSTMENT', '2022-10-20', 2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-3', 'some adjustment'); (subscriptionEntryUuid, membership.uuid, 'SUBSCRIPTION', '2022-10-20', 2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-3', 'some subscription', null),
(uuid_generate_v4(), membership.uuid, 'ADJUSTMENT', '2022-10-21', -2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-4', 'some adjustment', subscriptionEntryUuid);
end; $$; end; $$;
--// --//

View File

@ -10,8 +10,7 @@
*/ */
create or replace procedure createHsBookingItemTransactionTestData( create or replace procedure createHsBookingItemTransactionTestData(
givenPartnerNumber numeric, givenPartnerNumber numeric,
givenDebitorSuffix char(2), givenDebitorSuffix char(2)
givenCaption varchar
) )
language plpgsql as $$ language plpgsql as $$
declare declare
@ -33,7 +32,9 @@ begin
raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor; raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor;
insert insert
into hs_booking_item (uuid, debitoruuid, caption, validity, resources) into hs_booking_item (uuid, debitoruuid, caption, validity, resources)
values (uuid_generate_v4(), relatedDebitor.uuid, givenCaption, daterange('20221001' , null, '[]'), '{ "CPUs": 2, "HDD-storage": 512 }'::jsonb); values (uuid_generate_v4(), relatedDebitor.uuid, 'some ManagedServer', daterange('20221001', null, '[]'), '{ "CPUs": 2, "SDD-storage": 512 }'::jsonb),
(uuid_generate_v4(), relatedDebitor.uuid, 'some CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "HDD-storage": 1024 }'::jsonb),
(uuid_generate_v4(), relatedDebitor.uuid, 'some Whatever', daterange('20240401', null, '[]'), '{ "CPUs": 1, "SDD-storage": 512, "HDD-storage": 2048 }'::jsonb);
end; $$; end; $$;
--// --//
@ -44,8 +45,8 @@ end; $$;
do language plpgsql $$ do language plpgsql $$
begin begin
call createHsBookingItemTransactionTestData(10001, '11', 'some booking 1'); call createHsBookingItemTransactionTestData(10001, '11');
call createHsBookingItemTransactionTestData(10002, '12', 'some booking 2'); call createHsBookingItemTransactionTestData(10002, '12');
call createHsBookingItemTransactionTestData(10003, '13', 'some booking 3'); call createHsBookingItemTransactionTestData(10003, '13');
end; end;
$$; $$;

View File

@ -43,6 +43,7 @@ public class ArchitectureTest {
"..hs.office.person", "..hs.office.person",
"..hs.office.relation", "..hs.office.relation",
"..hs.office.sepamandate", "..hs.office.sepamandate",
"..hs.booking.item",
"..errors", "..errors",
"..mapper", "..mapper",
"..ping", "..ping",
@ -116,11 +117,22 @@ public class ArchitectureTest {
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsAdminPackagesRule = classes() public static final ArchRule hsOfficePackageAccessRule = classes()
.that().resideInAPackage("..hs.office.(*)..") .that().resideInAPackage("..hs.office.(*)..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage( .resideInAnyPackage(
"..hs.office.(*)..", "..hs.office.(*)..",
"..hs.booking.(*)..",
"..rbac.rbacgrant" // TODO.test: just because of RbacGrantsDiagramServiceIntegrationTest
);
@ArchTest
@SuppressWarnings("unused")
public static final ArchRule hsBookingPackageAccessRule = classes()
.that().resideInAPackage("..hs.booking.(*)..")
.should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage(
"..hs.booking.(*)..",
"..rbac.rbacgrant" // TODO.test: just because of RbacGrantsDiagramServiceIntegrationTest "..rbac.rbacgrant" // TODO.test: just because of RbacGrantsDiagramServiceIntegrationTest
); );

View File

@ -6,9 +6,7 @@ import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.test.Accepts;
import net.hostsharing.test.JpaAttempt; import net.hostsharing.test.JpaAttempt;
import org.json.JSONException;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -19,8 +17,10 @@ import org.springframework.transaction.annotation.Transactional;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static java.util.Map.entry;
import static net.hostsharing.test.JsonMatcher.lenientlyEquals; import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
@ -51,7 +51,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
class ListBookingItems { class ListBookingItems {
@Test @Test
void globalAdmin_canViewAllBookingItemsOfArbitraryDebitor() throws JSONException { void globalAdmin_canViewAllBookingItemsOfArbitraryDebitor() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
@ -62,28 +62,31 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/booking/items?debitorUuid" + givenDebitor.getUuid()) .get("http://localhost/api/hs/booking/items?debitorUuid=" + givenDebitor.getUuid())
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
.log().all() .log().all()
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"debitor": { "debitorNumber": 1000111 }, "caption": "some ManagedServer",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": "2026-12-31" "validTo": null,
}, "resources": null
{ },
"debitor": { "debitorNumber": 1000212 }, {
"validFrom": "2022-10-01", "caption": "some CloudServer",
"validTo": "2026-12-31" "validFrom": "2023-01-15",
}, "validTo": "2024-04-14",
{ "resources": null
"debitor": { "debitorNumber": 1000313 }, },
"validFrom": "2022-10-01", {
"validTo": "2026-12-31" "caption": "some Whatever",
} "validFrom": "2024-04-01",
"validTo": null,
"resources": null
}
] ]
""")); """));
// @formatter:on // @formatter:on
@ -97,30 +100,35 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
void globalAdmin_canAddBookingItem() { void globalAdmin_canAddBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("Third").get(0); final var givenDebitor = debitorRepo.findDebitorByDebitorNumber(1000111).get(0);
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
.given() .given()
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"debitorUuid": "%s", "debitorUuid": "%s",
"validFrom": "2022-10-13" "caption": "some new booking",
} "resources": {
"something": 12
},
"validFrom": "2022-10-13"
}
""".formatted(givenDebitor.getUuid())) """.formatted(givenDebitor.getUuid()))
.port(port) .port(port)
.when() .when()
.post("http://localhost/api/hs/office/BookingItems") .post("http://localhost/api/hs/booking/items")
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(201) .statusCode(201)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"debitor": { "debitorNumber": 1000111 }, "caption": "some new booking",
"validFrom": "2022-10-01", "validFrom": "2022-10-13",
"validTo": "2026-12-31" "validTo": null,
} "resources": null
}
""")) """))
.header("Location", startsWith("http://localhost")) .header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on .extract().header("Location"); // @formatter:on
@ -139,7 +147,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
void globalAdmin_canGetArbitraryBookingItem() { void globalAdmin_canGetArbitraryBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItemUuid = bookingItemRepo.findAll().stream() final var givenBookingItemUuid = bookingItemRepo.findAll().stream()
.filter(bi -> bi.getDebitor().getDebitorNumber() == 1000101) .filter(bi -> bi.getDebitor().getDebitorNumber() == 1000111)
.findAny().orElseThrow().getUuid(); .findAny().orElseThrow().getUuid();
RestAssured // @formatter:off RestAssured // @formatter:off
@ -148,15 +156,16 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/booking/items/" + givenBookingItemUuid) .get("http://localhost/api/hs/booking/items/" + givenBookingItemUuid)
.then().log().body().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"debitor": { "debitorNumber": 1000111 }, "caption": "some CloudServer",
"validFrom": "2022-10-01", "validFrom": "2023-01-15",
"validTo": "2026-12-31" "validTo": "2024-04-14",
} "resources": null
}
""")); // @formatter:on """)); // @formatter:on
} }
@ -164,8 +173,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
void normalUser_canNotGetUnrelatedBookingItem() { void normalUser_canNotGetUnrelatedBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItemUuid = bookingItemRepo.findAll().stream() final var givenBookingItemUuid = bookingItemRepo.findAll().stream()
.filter(bi -> bi.getDebitor().getDebitorNumber() == 1000101) .filter(bi -> bi.getDebitor().getDebitorNumber() == 1000212)
.findAny().orElseThrow().getUuid(); .map(HsBookingItemEntity::getUuid)
.findAny().orElseThrow();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -181,23 +191,25 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
void debitorAgentUser_canGetRelatedBookingItem() { void debitorAgentUser_canGetRelatedBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItemUuid = bookingItemRepo.findAll().stream() final var givenBookingItemUuid = bookingItemRepo.findAll().stream()
.filter(bi -> bi.getDebitor().getDebitorNumber() == 1000101) .filter(bi -> bi.getDebitor().getDebitorNumber() == 1000313)
.findAny().orElseThrow().getUuid(); .findAny().orElseThrow().getUuid();
generateRbacDiagramForObjectPermission(givenBookingItemUuid, "SELECT", "booking-item-of-debitor-1000313");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-user", "person-FirbySusan@example.com") .header("current-user", "person-TuckerJack@example.com")
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/booking/items/" + givenBookingItemUuid) .get("http://localhost/api/hs/booking/items/" + givenBookingItemUuid)
.then().log().body().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"debitor": { "debitorNumber": 1000111 }, "caption": "some CloudServer",
"validFrom": "2022-10-01", "validFrom": "2023-01-15",
"validTo": "2026-12-31" "validTo": "2024-04-14",
"resources": null
} }
""")); // @formatter:on """)); // @formatter:on
} }
@ -227,16 +239,17 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
""") """)
.port(port) .port(port)
.when() .when()
.patch("http://localhost/api/hs/office/BookingItems/" + givenBookingItem.getUuid()) .patch("http://localhost/api/hs/booking/items/" + givenBookingItem.getUuid())
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"debitor": { "debitorNumber": 1000111 }, "caption": "some test-booking",
"validFrom": "2022-10-01", "validFrom": "2020-06-05",
"validTo": "2026-12-31" "validTo": "2022-12-31",
} "resources": null
}
""")); // @formatter:on """)); // @formatter:on
// finally, the bookingItem is actually updated // finally, the bookingItem is actually updated
@ -267,15 +280,16 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
""") """)
.port(port) .port(port)
.when() .when()
.patch("http://localhost/api/hs/office/BookingItems/" + givenBookingItem.getUuid()) .patch("http://localhost/api/hs/booking/items/" + givenBookingItem.getUuid())
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"debitor": { "debitorNumber": 1000111 }, "caption": "some test-booking",
"validFrom": "2022-10-01", "validFrom": "2022-11-01",
"validTo": "2026-12-31" "validTo": "2022-12-31",
"resources": null
} }
""")); // @formatter:on """)); // @formatter:on
@ -305,7 +319,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
""") """)
.port(port) .port(port)
.when() .when()
.patch("http://localhost/api/hs/office/BookingItems/" + givenBookingItem.getUuid()) .patch("http://localhost/api/hs/booking/items/" + givenBookingItem.getUuid())
.then().assertThat() .then().assertThat()
// TODO.impl: I'd prefer a 400, // TODO.impl: I'd prefer a 400,
// but OpenApi Spring Code Gen does not convert additonalProperties=false into a validation // but OpenApi Spring Code Gen does not convert additonalProperties=false into a validation
@ -333,7 +347,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.port(port) .port(port)
.when() .when()
.delete("http://localhost/api/hs/office/BookingItems/" + givenBookingItem.getUuid()) .delete("http://localhost/api/hs/booking/items/" + givenBookingItem.getUuid())
.then().log().body().assertThat() .then().log().body().assertThat()
.statusCode(204); // @formatter:on .statusCode(204); // @formatter:on
@ -342,25 +356,6 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
} }
@Test @Test
void bankAccountAdminUser_canNotDeleteRelatedBookingItem() {
context.define("superuser-alex@hostsharing.net");
final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111);
RestAssured // @formatter:off
.given()
.header("current-user", "bankaccount-admin@FirstGmbH.example.com")
.port(port)
.when()
.delete("http://localhost/api/hs/office/BookingItems/" + givenBookingItem.getUuid())
.then().log().body().assertThat()
.statusCode(403); // @formatter:on
// then the given bookingItem is still there
assertThat(bookingItemRepo.findByUuid(givenBookingItem.getUuid())).isNotEmpty();
}
@Test
@Accepts({ "BookingItem:X(Access Control)" })
void normalUser_canNotDeleteUnrelatedBookingItem() { void normalUser_canNotDeleteUnrelatedBookingItem() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111); final var givenBookingItem = givenSomeTemporaryBookingItemForDebitorNumber(1000111);
@ -370,7 +365,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.header("current-user", "selfregistered-user-drew@hostsharing.org") .header("current-user", "selfregistered-user-drew@hostsharing.org")
.port(port) .port(port)
.when() .when()
.delete("http://localhost/api/hs/office/BookingItems/" + givenBookingItem.getUuid()) .delete("http://localhost/api/hs/booking/items/" + givenBookingItem.getUuid())
.then().log().body().assertThat() .then().log().body().assertThat()
.statusCode(404); // @formatter:on .statusCode(404); // @formatter:on
@ -386,6 +381,8 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
final var newBookingItem = HsBookingItemEntity.builder() final var newBookingItem = HsBookingItemEntity.builder()
.uuid(UUID.randomUUID()) .uuid(UUID.randomUUID())
.debitor(givenDebitor) .debitor(givenDebitor)
.caption("some test-booking")
.resources(Map.ofEntries(entry("something", 1)))
.validity(Range.closedOpen( .validity(Range.closedOpen(
LocalDate.parse("2022-11-01"), LocalDate.parse("2023-03-31"))) LocalDate.parse("2022-11-01"), LocalDate.parse("2023-03-31")))
.build(); .build();

View File

@ -148,7 +148,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
public void globalAdmin_withoutAssumedRole_canViewAllBookingItemsOfArbitraryDebitor() { public void globalAdmin_withoutAssumedRole_canViewAllBookingItemsOfArbitraryDebitor() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var debitorUuid = debitorRepo.findDebitorByDebitorNumber(1000111).stream().findAny().orElseThrow().getUuid(); final var debitorUuid = debitorRepo.findDebitorByDebitorNumber(1000212).stream().findAny().orElseThrow().getUuid();
// when // when
final var result = bookingItemRepo.findAllByDebitorUuid(debitorUuid); final var result = bookingItemRepo.findAllByDebitorUuid(debitorUuid);
@ -156,7 +156,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// then // then
allTheseBookingItemsAreReturned( allTheseBookingItemsAreReturned(
result, result,
"HsBookingItemEntity(D-1000111, some booking 1, [2022-10-01,), {CPUs=2, HDD-storage=512})"); "HsBookingItemEntity(D-1000212, some CloudServer, [2023-01-15,2024-04-15), {CPUs=2, HDD-storage=1024})",
"HsBookingItemEntity(D-1000212, some ManagedServer, [2022-10-01,), {CPUs=2, SDD-storage=512})",
"HsBookingItemEntity(D-1000212, some Whatever, [2024-04-01,), {CPUs=1, HDD-storage=2048, SDD-storage=512})");
} }
@Test @Test
@ -171,7 +173,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// then: // then:
exactlyTheseBookingItemsAreReturned( exactlyTheseBookingItemsAreReturned(
result, result,
"HsBookingItemEntity(D-1000111, some booking 1, [2022-10-01,), {CPUs=2, HDD-storage=512})"); "HsBookingItemEntity(D-1000111, some CloudServer, [2023-01-15,2024-04-15), {CPUs=2, HDD-storage=1024})",
"HsBookingItemEntity(D-1000111, some ManagedServer, [2022-10-01,), {CPUs=2, SDD-storage=512})",
"HsBookingItemEntity(D-1000111, some Whatever, [2024-04-01,), {CPUs=1, HDD-storage=2048, SDD-storage=512})");
} }
} }

View File

@ -110,14 +110,28 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
"assetValue": 128.00, "assetValue": 128.00,
"valueDate": "2022-10-20", "valueDate": "2022-10-20",
"reference": "ref 1000202-3", "reference": "ref 1000202-3",
"comment": "some loss" "comment": "some loss",
"adjustmentAssetTx": {
"transactionType": "ADJUSTMENT",
"assetValue": -128.00,
"valueDate": "2022-10-21",
"reference": "ref 1000202-3",
"comment": "some adjustment"
}
}, },
{ {
"transactionType": "ADJUSTMENT", "transactionType": "ADJUSTMENT",
"assetValue": -128.00, "assetValue": -128.00,
"valueDate": "2022-10-21", "valueDate": "2022-10-21",
"reference": "ref 1000202-3", "reference": "ref 1000202-3",
"comment": "some adjustment" "comment": "some adjustment",
"adjustedAssetTx": {
"transactionType": "DEPOSIT",
"assetValue": 128.00,
"valueDate": "2022-10-20",
"reference": "ref 1000202-3",
"comment": "some loss"
}
} }
] ]
""")); // @formatter:on """)); // @formatter:on

View File

@ -45,27 +45,27 @@ class HsOfficeCoopAssetsTransactionEntityUnitTest {
final var result = givenCoopAssetTransaction.toString(); final var result = givenCoopAssetTransaction.toString();
assertThat(result).isEqualTo("CoopAssetsTransaction(M-1000101: 2020-01-01, DEPOSIT, 128.00, some-ref, some comment, M-1000101:-128.00)"); assertThat(result).isEqualTo("CoopAssetsTransaction(M-1000101: 2020-01-01, DEPOSIT, 128.00, some-ref, some comment, M-1000101:ADJ:-128.00)");
} }
@Test @Test
void toShortStringContainsOnlyMemberNumberSuffixAndSharesCountOnly() { void toShortStringContainsOnlyMemberNumberSuffixAndSharesCountOnly() {
final var result = givenCoopAssetTransaction.toShortString(); final var result = givenCoopAssetTransaction.toShortString();
assertThat(result).isEqualTo("M-1000101:+128.00"); assertThat(result).isEqualTo("M-1000101:DEP:+128.00");
} }
@Test @Test
void toStringWithEmptyTransactionDoesNotThrowException() { void toStringWithEmptyTransactionDoesNotThrowException() {
final var result = givenEmptyCoopAssetsTransaction.toString(); final var result = givenEmptyCoopAssetsTransaction.toString();
assertThat(result).isEqualTo("CoopAssetsTransaction(M-?????: )"); assertThat(result).isEqualTo("CoopAssetsTransaction(M-???????: )");
} }
@Test @Test
void toShortStringEmptyTransactionDoesNotThrowException() { void toShortStringEmptyTransactionDoesNotThrowException() {
final var result = givenEmptyCoopAssetsTransaction.toShortString(); final var result = givenEmptyCoopAssetsTransaction.toShortString();
assertThat(result).isEqualTo("M-?????:+0.00"); assertThat(result).isEqualTo("M-???????:nul:+0.00");
} }
} }

View File

@ -142,18 +142,18 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
result, result,
"CoopAssetsTransaction(M-1000101: 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)", "CoopAssetsTransaction(M-1000101: 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)",
"CoopAssetsTransaction(M-1000101: 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)", "CoopAssetsTransaction(M-1000101: 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)",
"CoopAssetsTransaction(M-1000101: 2022-10-20, DEPOSIT, 128.00, ref 1000101-3, some loss)", "CoopAssetsTransaction(M-1000101: 2022-10-20, DEPOSIT, 128.00, ref 1000101-3, some loss, M-1000101:ADJ:-128.00)",
"CoopAssetsTransaction(M-1000101: 2022-10-21, ADJUSTMENT, -128.00, ref 1000101-3, some adjustment, M-1000101:+128.00)", "CoopAssetsTransaction(M-1000101: 2022-10-21, ADJUSTMENT, -128.00, ref 1000101-3, some adjustment, M-1000101:DEP:+128.00)",
"CoopAssetsTransaction(M-1000202: 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)", "CoopAssetsTransaction(M-1000202: 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)",
"CoopAssetsTransaction(M-1000202: 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)", "CoopAssetsTransaction(M-1000202: 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)",
"CoopAssetsTransaction(M-1000202: 2022-10-20, DEPOSIT, 128.00, ref 1000202-3, some loss)", "CoopAssetsTransaction(M-1000202: 2022-10-20, DEPOSIT, 128.00, ref 1000202-3, some loss, M-1000202:ADJ:-128.00)",
"CoopAssetsTransaction(M-1000202: 2022-10-21, ADJUSTMENT, -128.00, ref 1000202-3, some adjustment, M-1000202:+128.00)", "CoopAssetsTransaction(M-1000202: 2022-10-21, ADJUSTMENT, -128.00, ref 1000202-3, some adjustment, M-1000202:DEP:+128.00)",
"CoopAssetsTransaction(M-1000303: 2010-03-15, DEPOSIT, 320.00, ref 1000303-1, initial deposit)", "CoopAssetsTransaction(M-1000303: 2010-03-15, DEPOSIT, 320.00, ref 1000303-1, initial deposit)",
"CoopAssetsTransaction(M-1000303: 2021-09-01, DISBURSAL, -128.00, ref 1000303-2, partial disbursal)", "CoopAssetsTransaction(M-1000303: 2021-09-01, DISBURSAL, -128.00, ref 1000303-2, partial disbursal)",
"CoopAssetsTransaction(M-1000303: 2022-10-20, DEPOSIT, 128.00, ref 1000303-3, some loss)", "CoopAssetsTransaction(M-1000303: 2022-10-20, DEPOSIT, 128.00, ref 1000303-3, some loss, M-1000303:ADJ:-128.00)",
"CoopAssetsTransaction(M-1000303: 2022-10-21, ADJUSTMENT, -128.00, ref 1000303-3, some adjustment, M-1000303:+128.00)"); "CoopAssetsTransaction(M-1000303: 2022-10-21, ADJUSTMENT, -128.00, ref 1000303-3, some adjustment, M-1000303:DEP:+128.00)");
} }
@Test @Test
@ -173,8 +173,8 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
result, result,
"CoopAssetsTransaction(M-1000202: 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)", "CoopAssetsTransaction(M-1000202: 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)",
"CoopAssetsTransaction(M-1000202: 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)", "CoopAssetsTransaction(M-1000202: 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)",
"CoopAssetsTransaction(M-1000202: 2022-10-20, DEPOSIT, 128.00, ref 1000202-3, some loss)", "CoopAssetsTransaction(M-1000202: 2022-10-20, DEPOSIT, 128.00, ref 1000202-3, some loss, M-1000202:ADJ:-128.00)",
"CoopAssetsTransaction(M-1000202: 2022-10-21, ADJUSTMENT, -128.00, ref 1000202-3, some adjustment, M-1000202:+128.00)"); "CoopAssetsTransaction(M-1000202: 2022-10-21, ADJUSTMENT, -128.00, ref 1000202-3, some adjustment, M-1000202:DEP:+128.00)");
} }
@Test @Test
@ -211,8 +211,8 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
result, result,
"CoopAssetsTransaction(M-1000101: 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)", "CoopAssetsTransaction(M-1000101: 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)",
"CoopAssetsTransaction(M-1000101: 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)", "CoopAssetsTransaction(M-1000101: 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)",
"CoopAssetsTransaction(M-1000101: 2022-10-20, DEPOSIT, 128.00, ref 1000101-3, some loss)", "CoopAssetsTransaction(M-1000101: 2022-10-20, DEPOSIT, 128.00, ref 1000101-3, some loss, M-1000101:ADJ:-128.00)",
"CoopAssetsTransaction(M-1000101: 2022-10-21, ADJUSTMENT, -128.00, ref 1000101-3, some adjustment, M-1000101:+128.00)"); "CoopAssetsTransaction(M-1000101: 2022-10-21, ADJUSTMENT, -128.00, ref 1000101-3, some adjustment, M-1000101:DEP:+128.00)");
} }
} }

View File

@ -4,6 +4,8 @@ import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRawEntity;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository;
import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.test.Accepts; import net.hostsharing.test.Accepts;
@ -19,9 +21,12 @@ import org.springframework.transaction.annotation.Transactional;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionType.DEPOSIT;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid; import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
import static net.hostsharing.test.JsonMatcher.lenientlyEquals; import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -69,7 +74,15 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
void globalAdmin_canViewAllCoopSharesTransactions() { void globalAdmin_canViewAllCoopSharesTransactions() {
RestAssured // @formatter:off 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 .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(12)); // @formatter:on
} }
@Test @Test
@ -95,12 +108,33 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
"reference": "ref 1000202-2", "reference": "ref 1000202-2",
"comment": "cancelling some" "comment": "cancelling some"
}, },
{
"transactionType": "SUBSCRIPTION",
"shareCount": 2,
"valueDate": "2022-10-20",
"reference": "ref 1000202-3",
"comment": "some subscription",
"adjustmentShareTx": {
"transactionType": "ADJUSTMENT",
"shareCount": -2,
"valueDate": "2022-10-21",
"reference": "ref 1000202-4",
"comment": "some adjustment"
}
},
{ {
"transactionType": "ADJUSTMENT", "transactionType": "ADJUSTMENT",
"shareCount": 2, "shareCount": -2,
"valueDate": "2022-10-20", "valueDate": "2022-10-21",
"reference": "ref 1000202-3", "reference": "ref 1000202-4",
"comment": "some adjustment" "comment": "some adjustment",
"adjustedShareTx": {
"transactionType": "SUBSCRIPTION",
"shareCount": 2,
"valueDate": "2022-10-20",
"reference": "ref 1000202-3",
"comment": "some subscription"
}
} }
] ]
""")); // @formatter:on """)); // @formatter:on
@ -159,8 +193,76 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
""")).header("Location", startsWith("http://localhost")).extract().header("Location"); // @formatter:on """)).header("Location", startsWith("http://localhost")).extract().header("Location"); // @formatter:on
// finally, the new coopSharesTransaction can be accessed under the generated UUID // finally, the new coopSharesTransaction can be accessed under the generated UUID
final var newUserUuid = UUID.fromString(location.substring(location.lastIndexOf('/') + 1)); final var newShareTxUuid = UUID.fromString(location.substring(location.lastIndexOf('/') + 1));
assertThat(newUserUuid).isNotNull(); assertThat(newShareTxUuid).isNotNull();
}
@Test
void globalAdmin_canAddCoopSharesAdjustmentTransaction() {
context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101);
final var givenTransaction = jpaAttempt.transacted(() -> {
// TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...)
context.define("superuser-alex@hostsharing.net");
return coopSharesTransactionRepo.save(HsOfficeCoopSharesTransactionEntity.builder()
.transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION)
.valueDate(LocalDate.of(2022, 10, 20))
.membership(givenMembership)
.shareCount(13)
.reference("test ref")
.build());
}).assertSuccessful().assertNotNull().returnedValue();
toCleanup(HsOfficeCoopSharesTransactionRawEntity.class, givenTransaction.getUuid());
final var location = RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"membershipUuid": "%s",
"transactionType": "ADJUSTMENT",
"shareCount": %s,
"valueDate": "2022-10-30",
"reference": "test ref adjustment",
"comment": "some coop shares adjustment transaction",
"adjustedShareTxUuid": "%s"
}
""".formatted(
givenMembership.getUuid(),
-givenTransaction.getShareCount(),
givenTransaction.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": "ADJUSTMENT",
"shareCount": -13,
"valueDate": "2022-10-30",
"reference": "test ref adjustment",
"comment": "some coop shares adjustment transaction",
"adjustedShareTx": {
"transactionType": "SUBSCRIPTION",
"shareCount": 13,
"valueDate": "2022-10-20",
"reference": "test ref"
}
}
"""))
.header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on
// finally, the new coopAssetsTransaction can be accessed under the generated UUID
final var newShareTxUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
assertThat(newShareTxUuid).isNotNull();
toCleanup(HsOfficeCoopSharesTransactionRawEntity.class, newShareTxUuid);
} }
@Test @Test
@ -169,7 +271,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101);
final var location = RestAssured // @formatter:off RestAssured // @formatter:off
.given().header("current-user", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body(""" .given().header("current-user", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body("""
{ {
"membershipUuid": "%s", "membershipUuid": "%s",

View File

@ -1,7 +1,10 @@
package net.hostsharing.hsadminng.hs.office.coopshares; package net.hostsharing.hsadminng.hs.office.coopshares;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionType;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import static net.hostsharing.hsadminng.hs.office.membership.TestHsMembership.TEST_MEMBERSHIP; import static net.hostsharing.hsadminng.hs.office.membership.TestHsMembership.TEST_MEMBERSHIP;
@ -15,34 +18,56 @@ class HsOfficeCoopSharesTransactionEntityUnitTest {
.valueDate(LocalDate.parse("2020-01-01")) .valueDate(LocalDate.parse("2020-01-01"))
.transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION) .transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION)
.shareCount(4) .shareCount(4)
.comment("some comment")
.build(); .build();
final HsOfficeCoopSharesTransactionEntity givenCoopShareAdjustmentTransaction = HsOfficeCoopSharesTransactionEntity.builder()
.membership(TEST_MEMBERSHIP)
.reference("some-ref")
.valueDate(LocalDate.parse("2020-01-15"))
.transactionType(HsOfficeCoopSharesTransactionType.ADJUSTMENT)
.shareCount(-4)
.comment("some comment")
.adjustedShareTx(givenCoopSharesTransaction)
.build();
final HsOfficeCoopSharesTransactionEntity givenEmptyCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder().build(); final HsOfficeCoopSharesTransactionEntity givenEmptyCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder().build();
@Test @Test
void toStringContainsAlmostAllPropertiesAccount() { void toStringContainsAllNonNullProperties() {
final var result = givenCoopSharesTransaction.toString(); final var result = givenCoopSharesTransaction.toString();
assertThat(result).isEqualTo("CoopShareTransaction(M-1000101, 2020-01-01, SUBSCRIPTION, 4, some-ref)"); assertThat(result).isEqualTo("CoopShareTransaction(M-1000101: 2020-01-01, SUBSCRIPTION, 4, some-ref, some comment)");
} }
@Test @Test
void toShortStringContainsOnlyMemberNumberAndShareCountOnly() { void toStringWithReverseEntryContainsReverseEntry() {
givenCoopSharesTransaction.setAdjustedShareTx(givenCoopShareAdjustmentTransaction);
final var result = givenCoopSharesTransaction.toString();
assertThat(result).isEqualTo("CoopShareTransaction(M-1000101: 2020-01-01, SUBSCRIPTION, 4, some-ref, some comment, M-1000101:ADJ:-4)");
}
@Test
void toShortStringContainsOnlyAbbreviatedString() {
final var result = givenCoopSharesTransaction.toShortString(); final var result = givenCoopSharesTransaction.toShortString();
assertThat(result).isEqualTo("M-1000101+4"); assertThat(result).isEqualTo("M-1000101:SUB:+4");
} }
@Test @Test
void toStringEmptyTransactionDoesNotThrowException() { void toStringEmptyTransactionDoesNotThrowException() {
final var result = givenEmptyCoopSharesTransaction.toString(); final var result = givenEmptyCoopSharesTransaction.toString();
assertThat(result).isEqualTo("CoopShareTransaction(0)"); assertThat(result).isEqualTo("CoopShareTransaction(null: 0)");
} }
@Test @Test
void toShortStringEmptyTransactionDoesNotThrowException() { void toShortStringEmptyTransactionDoesNotThrowException() {
final var result = givenEmptyCoopSharesTransaction.toShortString(); final var result = givenEmptyCoopSharesTransaction.toShortString();
assertThat(result).isEqualTo("null+0"); assertThat(result).isEqualTo("null:nul:+0");
} }
} }

View File

@ -0,0 +1,18 @@
package net.hostsharing.hsadminng.hs.office.coopshares;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.NoArgsConstructor;
import java.util.UUID;
@Entity
@Table(name = "hs_office_coopsharestransaction")
@NoArgsConstructor
public class HsOfficeCoopSharesTransactionRawEntity {
@Id
private UUID uuid;
}

View File

@ -126,7 +126,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
class FindAllCoopSharesTransactions { class FindAllCoopSharesTransactions {
@Test @Test
public void globalAdmin_anViewAllCoopSharesTransactions() { public void globalAdmin_canViewAllCoopSharesTransactions() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
@ -137,19 +137,22 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
null); null);
// then // then
allTheseCoopSharesTransactionsAreReturned( exactlyTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(M-1000101, 2010-03-15, SUBSCRIPTION, 4, ref 1000101-1, initial subscription)", "CoopShareTransaction(M-1000101: 2010-03-15, SUBSCRIPTION, 4, ref 1000101-1, initial subscription)",
"CoopShareTransaction(M-1000101, 2021-09-01, CANCELLATION, -2, ref 1000101-2, cancelling some)", "CoopShareTransaction(M-1000101: 2021-09-01, CANCELLATION, -2, ref 1000101-2, cancelling some)",
"CoopShareTransaction(M-1000101, 2022-10-20, ADJUSTMENT, 2, ref 1000101-3, some adjustment)", "CoopShareTransaction(M-1000101: 2022-10-20, SUBSCRIPTION, 2, ref 1000101-3, some subscription, M-1000101:ADJ:-2)",
"CoopShareTransaction(M-1000101: 2022-10-21, ADJUSTMENT, -2, ref 1000101-4, some adjustment, M-1000101:SUB:+2)",
"CoopShareTransaction(M-1000202, 2010-03-15, SUBSCRIPTION, 4, ref 1000202-1, initial subscription)", "CoopShareTransaction(M-1000202: 2010-03-15, SUBSCRIPTION, 4, ref 1000202-1, initial subscription)",
"CoopShareTransaction(M-1000202, 2021-09-01, CANCELLATION, -2, ref 1000202-2, cancelling some)", "CoopShareTransaction(M-1000202: 2021-09-01, CANCELLATION, -2, ref 1000202-2, cancelling some)",
"CoopShareTransaction(M-1000202, 2022-10-20, ADJUSTMENT, 2, ref 1000202-3, some adjustment)", "CoopShareTransaction(M-1000202: 2022-10-20, SUBSCRIPTION, 2, ref 1000202-3, some subscription, M-1000202:ADJ:-2)",
"CoopShareTransaction(M-1000202: 2022-10-21, ADJUSTMENT, -2, ref 1000202-4, some adjustment, M-1000202:SUB:+2)",
"CoopShareTransaction(M-1000303, 2010-03-15, SUBSCRIPTION, 4, ref 1000303-1, initial subscription)", "CoopShareTransaction(M-1000303: 2010-03-15, SUBSCRIPTION, 4, ref 1000303-1, initial subscription)",
"CoopShareTransaction(M-1000303, 2021-09-01, CANCELLATION, -2, ref 1000303-2, cancelling some)", "CoopShareTransaction(M-1000303: 2021-09-01, CANCELLATION, -2, ref 1000303-2, cancelling some)",
"CoopShareTransaction(M-1000303, 2022-10-20, ADJUSTMENT, 2, ref 1000303-3, some adjustment)"); "CoopShareTransaction(M-1000303: 2022-10-20, SUBSCRIPTION, 2, ref 1000303-3, some subscription, M-1000303:ADJ:-2)",
"CoopShareTransaction(M-1000303: 2022-10-21, ADJUSTMENT, -2, ref 1000303-4, some adjustment, M-1000303:SUB:+2)");
} }
@Test @Test
@ -165,11 +168,12 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
null); null);
// then // then
allTheseCoopSharesTransactionsAreReturned( exactlyTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(M-1000202, 2010-03-15, SUBSCRIPTION, 4, ref 1000202-1, initial subscription)", "CoopShareTransaction(M-1000202: 2010-03-15, SUBSCRIPTION, 4, ref 1000202-1, initial subscription)",
"CoopShareTransaction(M-1000202, 2021-09-01, CANCELLATION, -2, ref 1000202-2, cancelling some)", "CoopShareTransaction(M-1000202: 2021-09-01, CANCELLATION, -2, ref 1000202-2, cancelling some)",
"CoopShareTransaction(M-1000202, 2022-10-20, ADJUSTMENT, 2, ref 1000202-3, some adjustment)"); "CoopShareTransaction(M-1000202: 2022-10-20, SUBSCRIPTION, 2, ref 1000202-3, some subscription, M-1000202:ADJ:-2)",
"CoopShareTransaction(M-1000202: 2022-10-21, ADJUSTMENT, -2, ref 1000202-4, some adjustment, M-1000202:SUB:+2)");
} }
@Test @Test
@ -187,7 +191,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopSharesTransactionsAreReturned( allTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(M-1000202, 2021-09-01, CANCELLATION, -2, ref 1000202-2, cancelling some)"); "CoopShareTransaction(M-1000202: 2021-09-01, CANCELLATION, -2, ref 1000202-2, cancelling some)");
} }
@Test @Test
@ -204,9 +208,10 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// then: // then:
exactlyTheseCoopSharesTransactionsAreReturned( exactlyTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(M-1000101, 2010-03-15, SUBSCRIPTION, 4, ref 1000101-1, initial subscription)", "CoopShareTransaction(M-1000101: 2010-03-15, SUBSCRIPTION, 4, ref 1000101-1, initial subscription)",
"CoopShareTransaction(M-1000101, 2021-09-01, CANCELLATION, -2, ref 1000101-2, cancelling some)", "CoopShareTransaction(M-1000101: 2021-09-01, CANCELLATION, -2, ref 1000101-2, cancelling some)",
"CoopShareTransaction(M-1000101, 2022-10-20, ADJUSTMENT, 2, ref 1000101-3, some adjustment)"); "CoopShareTransaction(M-1000101: 2022-10-20, SUBSCRIPTION, 2, ref 1000101-3, some subscription, M-1000101:ADJ:-2)",
"CoopShareTransaction(M-1000101: 2022-10-21, ADJUSTMENT, -2, ref 1000101-4, some adjustment, M-1000101:SUB:+2)");
} }
} }

View File

@ -181,6 +181,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
.containsExactlyInAnyOrder(Array.fromFormatted( .containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, initialGrantNames,
"{ grant perm:relation#FirstGmbH-with-DEBITOR-FourtheG:INSERT>sepamandate to role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN by system and assume }", "{ grant perm:relation#FirstGmbH-with-DEBITOR-FourtheG:INSERT>sepamandate to role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN by system and assume }",
"{ grant perm:relation#FirstGmbH-with-DEBITOR-FourtheG:INSERT>hs_booking_item to role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN by system and assume }",
// owner // owner
"{ grant perm:debitor#D-1000122:DELETE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER by system and assume }", "{ grant perm:debitor#D-1000122:DELETE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER by system and assume }",

View File

@ -360,10 +360,10 @@ public class ImportOfficeData extends ContextBasedTest {
assertThat(toFormattedString(coopShares)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(coopShares)).isEqualToIgnoringWhitespace("""
{ {
33443=CoopShareTransaction(M-1001700, 2000-12-06, SUBSCRIPTION, 20, initial share subscription), 33443=CoopShareTransaction(M-1001700, 2000-12-06, SUBSCRIPTION, 20, legacy data import, initial share subscription),
33451=CoopShareTransaction(M-1002000, 2000-12-06, SUBSCRIPTION, 2, initial share subscription), 33451=CoopShareTransaction(M-1002000, 2000-12-06, SUBSCRIPTION, 2, legacy data import, initial share subscription),
33701=CoopShareTransaction(M-1001700, 2005-01-10, SUBSCRIPTION, 40, increase), 33701=CoopShareTransaction(M-1001700, 2005-01-10, SUBSCRIPTION, 40, legacy data import, increase),
33810=CoopShareTransaction(M-1002000, 2016-12-31, CANCELLATION, 22, membership ended) 33810=CoopShareTransaction(M-1002000, 2016-12-31, CANCELLATION, 22, legacy data import, membership ended)
} }
"""); """);
} }
@ -396,7 +396,7 @@ public class ImportOfficeData extends ContextBasedTest {
34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, legacy data import, for cancellation D), 34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, legacy data import, for cancellation D),
34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, legacy data import, for cancellation D), 34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, legacy data import, for cancellation D),
35001=CoopAssetsTransaction(M-1909000: 2024-01-15, DEPOSIT, 128.00, legacy data import, for subscription E), 35001=CoopAssetsTransaction(M-1909000: 2024-01-15, DEPOSIT, 128.00, legacy data import, for subscription E),
35002=CoopAssetsTransaction(M-1909000: 2024-01-20, ADJUSTMENT, -128.00, legacy data import, chargeback for subscription E, M-1909000:+128.00) 35002=CoopAssetsTransaction(M-1909000: 2024-01-20, ADJUSTMENT, -128.00, legacy data import, chargeback for subscription E, M-1909000:DEP:+128.00)
} }
"""); """);
} }
@ -803,8 +803,19 @@ public class ImportOfficeData extends ContextBasedTest {
) )
.shareCount(rec.getInteger("quantity")) .shareCount(rec.getInteger("quantity"))
.comment( rec.getString("comment")) .comment( rec.getString("comment"))
.reference("legacy data import") // TODO.spec: or use value from comment column?
.build(); .build();
if (shareTransaction.getTransactionType() == HsOfficeCoopSharesTransactionType.ADJUSTMENT) {
final var negativeValue = -shareTransaction.getShareCount();
final var adjustedShareTx = coopShares.values().stream().filter(a ->
a.getTransactionType() != HsOfficeCoopSharesTransactionType.ADJUSTMENT &&
a.getMembership() == shareTransaction.getMembership() &&
a.getShareCount() == negativeValue)
.findAny()
.orElseThrow(() -> new IllegalStateException("cannot determine share reverse entry for adjustment " + shareTransaction));
shareTransaction.setAdjustedShareTx(adjustedShareTx);
}
coopShares.put(rec.getInteger("member_share_id"), shareTransaction); coopShares.put(rec.getInteger("member_share_id"), shareTransaction);
}); });
} }