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>
This commit is contained in:
Marc Sandlus 2024-04-12 11:29:26 +02:00
parent 216886e5f4
commit b0a28200f9
10 changed files with 276 additions and 49 deletions

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

@ -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

@ -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

@ -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)
} }
"""); """);
} }
@ -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);
}); });
} }