From 74146998b5c1259748c96f637e0326ae79fa562c Mon Sep 17 00:00:00 2001 From: Serghei Motpan Date: Thu, 15 Aug 2024 20:43:53 +0300 Subject: [PATCH] Enhance expense jar category with name --- .../spendingplan/adapter/web/JarController.java | 2 +- .../application/AddOrRemoveJarCategoryService.java | 6 +++--- .../application/AddOrRemoveJarCategoryUseCase.java | 2 +- .../application/CategoriesJarCommandValidator.java | 7 +++---- .../spendingplan/domain/JarExpenseCategory.java | 12 ++++++++---- .../V1_08__spending_jar_expense_category_schema.sql | 13 +++++++------ .../migration/V1_09__jar_expense_record_schema.sql | 6 +++--- server/src/main/resources/openapi.yml | 4 ++++ .../io/myfinbox/spendingplan/DataSamples.groovy | 2 ++ .../AddOrRemoveJarCategoryServiceSpec.groovy | 7 +++++-- .../messaging/create-complete-plan-structure.sql | 4 ++++ .../web/jar_expense_category-create.sql | 3 +++ 12 files changed, 44 insertions(+), 24 deletions(-) diff --git a/server/src/main/java/io/myfinbox/spendingplan/adapter/web/JarController.java b/server/src/main/java/io/myfinbox/spendingplan/adapter/web/JarController.java index 5f362fc..5d9c95b 100644 --- a/server/src/main/java/io/myfinbox/spendingplan/adapter/web/JarController.java +++ b/server/src/main/java/io/myfinbox/spendingplan/adapter/web/JarController.java @@ -86,7 +86,7 @@ private JarCategoriesCommand toCommand(JarCategoryModificationResource resource) var categoryToAdds = resource.getCategories() .stream() .filter(Objects::nonNull) // avoid null categoryToAdd - .map(categoryToAdd -> new JarCategoryToAddOrRemove(categoryToAdd.getCategoryId(), categoryToAdd.getToAdd())) + .map(categoryToAdd -> new JarCategoryToAddOrRemove(categoryToAdd.getCategoryId(), categoryToAdd.getCategoryName(), categoryToAdd.getToAdd())) .toList(); return new JarCategoriesCommand(categoryToAdds); } diff --git a/server/src/main/java/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryService.java b/server/src/main/java/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryService.java index cf96856..fb0204a 100644 --- a/server/src/main/java/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryService.java +++ b/server/src/main/java/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryService.java @@ -86,10 +86,10 @@ private Either> validate(UUID planId, UUID jar private List filterToCreate(Jar jar, JarCategoriesCommand command) { return command.categories().stream() .filter(JarCategoryToAddOrRemove::toAdd) - .map(JarCategoryToAddOrRemove::categoryId) - .filter(category -> !existsByJarIdAndCategoryId(jar.getId(), category)) + .filter(category -> !existsByJarIdAndCategoryId(jar.getId(), category.categoryId())) .map(category -> JarExpenseCategory.builder() - .categoryId(new CategoryIdentifier(category)) + .categoryId(new CategoryIdentifier(category.categoryId())) + .categoryName(category.categoryName()) .jar(jar) .build()) .toList(); diff --git a/server/src/main/java/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryUseCase.java b/server/src/main/java/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryUseCase.java index 3be6a45..09231b4 100644 --- a/server/src/main/java/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryUseCase.java +++ b/server/src/main/java/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryUseCase.java @@ -30,7 +30,7 @@ record JarCategoriesCommand(List categories) { public static final String CATEGORIES_JAR_FIELD = "categories"; } - record JarCategoryToAddOrRemove(UUID categoryId, Boolean toAdd) { + record JarCategoryToAddOrRemove(UUID categoryId, String categoryName, Boolean toAdd) { /** * Gets whether the category is checked or not. diff --git a/server/src/main/java/io/myfinbox/spendingplan/application/CategoriesJarCommandValidator.java b/server/src/main/java/io/myfinbox/spendingplan/application/CategoriesJarCommandValidator.java index f8b3b46..7efba36 100644 --- a/server/src/main/java/io/myfinbox/spendingplan/application/CategoriesJarCommandValidator.java +++ b/server/src/main/java/io/myfinbox/spendingplan/application/CategoriesJarCommandValidator.java @@ -4,7 +4,6 @@ import io.vavr.control.Validation; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; import static io.myfinbox.shared.Failure.FieldViolation; @@ -13,6 +12,7 @@ import static io.vavr.API.Invalid; import static io.vavr.API.Valid; import static java.util.Objects.isNull; +import static org.apache.commons.lang3.StringUtils.isBlank; final class CategoriesJarCommandValidator { @@ -26,13 +26,12 @@ Validation> validate(List isNull(category.categoryId()) || isBlank(category.categoryName())); if (anyNull) { return Invalid(Failure.FieldViolation.builder() .field(CATEGORIES_JAR_FIELD) - .message("Null categoryId not allowed.") + .message("Null categoryId or blank categoryName not allowed.") .rejectedValue(categories) .build()); } diff --git a/server/src/main/java/io/myfinbox/spendingplan/domain/JarExpenseCategory.java b/server/src/main/java/io/myfinbox/spendingplan/domain/JarExpenseCategory.java index 73c56ae..297820a 100644 --- a/server/src/main/java/io/myfinbox/spendingplan/domain/JarExpenseCategory.java +++ b/server/src/main/java/io/myfinbox/spendingplan/domain/JarExpenseCategory.java @@ -7,7 +7,7 @@ import java.util.ArrayList; import java.util.List; -import static io.myfinbox.shared.Guards.notNull; +import static io.myfinbox.shared.Guards.*; import static jakarta.persistence.CascadeType.ALL; import static jakarta.persistence.GenerationType.SEQUENCE; import static lombok.AccessLevel.PACKAGE; @@ -21,8 +21,8 @@ public class JarExpenseCategory { @Id - @GeneratedValue(strategy = SEQUENCE, generator = "jer_seq_id") - @SequenceGenerator(name = "jer_seq_id", sequenceName = "jer_seq_id", allocationSize = 1) + @GeneratedValue(strategy = SEQUENCE, generator = "sjec_seq_id") + @SequenceGenerator(name = "sjec_seq_id", sequenceName = "sjec_seq_id", allocationSize = 1) // https://vladmihalcea.com/migrate-hilo-hibernate-pooled/ private Long id; @@ -32,6 +32,9 @@ public class JarExpenseCategory { @AttributeOverride(name = "id", column = @Column(name = "category_id")) private final CategoryIdentifier categoryId; + @Column(name = "category_name", nullable = false) + private String categoryName; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "jar_id", referencedColumnName = "id", nullable = false) private final Jar jar; @@ -40,9 +43,10 @@ public class JarExpenseCategory { private List expenseRecords = new ArrayList<>(); @Builder - public JarExpenseCategory(Jar jar, CategoryIdentifier categoryId) { + public JarExpenseCategory(Jar jar, CategoryIdentifier categoryId, String categoryName) { this.jar = notNull(jar, "jar cannot be null."); this.categoryId = notNull(categoryId, "categoryId cannot be null."); + this.categoryName = notBlank(categoryName, "categoryName cannot be null."); this.creationTimestamp = Instant.now(); } } diff --git a/server/src/main/resources/db/migration/V1_08__spending_jar_expense_category_schema.sql b/server/src/main/resources/db/migration/V1_08__spending_jar_expense_category_schema.sql index 693feba..ce2bde5 100644 --- a/server/src/main/resources/db/migration/V1_08__spending_jar_expense_category_schema.sql +++ b/server/src/main/resources/db/migration/V1_08__spending_jar_expense_category_schema.sql @@ -1,9 +1,12 @@ +CREATE SEQUENCE IF NOT EXISTS sjec_seq_id START 1 INCREMENT 1; + CREATE TABLE IF NOT EXISTS spending_jar_expense_category ( - id BIGSERIAL PRIMARY KEY, - jar_id UUID NOT NULL, - category_id UUID NOT NULL, - creation_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + id BIGINT PRIMARY KEY DEFAULT nextval('sjec_seq_id'), + jar_id UUID NOT NULL, + category_id UUID NOT NULL, + category_name VARCHAR(100) NOT NULL, + creation_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (jar_id) REFERENCES spending_jars (id) ) ; @@ -11,5 +14,3 @@ CREATE TABLE IF NOT EXISTS spending_jar_expense_category CREATE UNIQUE INDEX IF NOT EXISTS unique_spending_jar_expense_category_id_idx ON spending_jar_expense_category (jar_id, category_id); CREATE INDEX IF NOT EXISTS search_expense_category_jar_id_idx ON spending_jar_expense_category (jar_id); - -CREATE SEQUENCE IF NOT EXISTS sjec_seq_id START 1 INCREMENT 1; diff --git a/server/src/main/resources/db/migration/V1_09__jar_expense_record_schema.sql b/server/src/main/resources/db/migration/V1_09__jar_expense_record_schema.sql index 85b4871..4fd1f35 100644 --- a/server/src/main/resources/db/migration/V1_09__jar_expense_record_schema.sql +++ b/server/src/main/resources/db/migration/V1_09__jar_expense_record_schema.sql @@ -1,6 +1,8 @@ +CREATE SEQUENCE IF NOT EXISTS jer_seq_id START 1 INCREMENT 1; + CREATE TABLE IF NOT EXISTS jar_expense_record ( - id BIGSERIAL PRIMARY KEY, + id BIGINT PRIMARY KEY DEFAULT nextval('jer_seq_id'), expense_id UUID NOT NULL, creation_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, category_id UUID NOT NULL, @@ -14,5 +16,3 @@ CREATE TABLE IF NOT EXISTS jar_expense_record ; CREATE INDEX IF NOT EXISTS search_jar_expense_category_jar_id_idx ON jar_expense_record (category_id, expense_date, jar_expense_category_id); - -CREATE SEQUENCE IF NOT EXISTS jer_seq_id START 1 INCREMENT 1; diff --git a/server/src/main/resources/openapi.yml b/server/src/main/resources/openapi.yml index bef4839..ea6073c 100644 --- a/server/src/main/resources/openapi.yml +++ b/server/src/main/resources/openapi.yml @@ -287,6 +287,10 @@ components: type: string format: uuid description: Unique identifier for the expense category. + categoryName: + type: string + example: Clothing + description: The expense category name. toAdd: type: boolean default: true diff --git a/server/src/test/groovy/io/myfinbox/spendingplan/DataSamples.groovy b/server/src/test/groovy/io/myfinbox/spendingplan/DataSamples.groovy index 5cee9ec..31a8197 100644 --- a/server/src/test/groovy/io/myfinbox/spendingplan/DataSamples.groovy +++ b/server/src/test/groovy/io/myfinbox/spendingplan/DataSamples.groovy @@ -35,6 +35,7 @@ class DataSamples { static expenseDate = "2024-03-23" static String jarDescription = "Necessities spending: Rent, Food, Bills etc." static String planDescription = "My basic plan for tracking expenses" + static String categoryName = 'Clothing' static AMOUNT = [ amount : amount, @@ -125,6 +126,7 @@ class DataSamples { static JAR_CATEGORY_TO_ADD_OR_REMOVE = [ categoryId: jarCategoryId, + categoryName: categoryName, toAdd : true ] diff --git a/server/src/test/groovy/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryServiceSpec.groovy b/server/src/test/groovy/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryServiceSpec.groovy index d42bc3e..25c4341 100644 --- a/server/src/test/groovy/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryServiceSpec.groovy +++ b/server/src/test/groovy/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryServiceSpec.groovy @@ -49,7 +49,9 @@ class AddOrRemoveJarCategoryServiceSpec extends Specification { categories | failMessage null | 'At least one category must be provided.' [] | 'At least one category must be provided.' - [newSampleJarCategoryToAddAsMap(categoryId: null)] | 'Null categoryId not allowed.' + [newSampleJarCategoryToAddAsMap(categoryId: null)] | 'Null categoryId or blank categoryName not allowed.' + [newSampleJarCategoryToAddAsMap(categoryName: null)] | 'Null categoryId or blank categoryName not allowed.' + [newSampleJarCategoryToAddAsMap(categoryName: '')] | 'Null categoryId or blank categoryName not allowed.' [newSampleJarCategoryToAddAsMap(), newSampleJarCategoryToAddAsMap()] | 'Duplicate category ids provided.' } @@ -85,7 +87,7 @@ class AddOrRemoveJarCategoryServiceSpec extends Specification { given: 'a new command' def command = newSampleJarCategoriesCommand(categories: [newSampleJarCategoryToAddAsMap()]) - 1 * jars.findByIdAndPlanId(_ as JarIdentifier, _ as PlanIdentifier) >>Optional.empty() + 1 * jars.findByIdAndPlanId(_ as JarIdentifier, _ as PlanIdentifier) >> Optional.empty() when: 'attempting to add or remove categories for null jar id' def either = service.addOrRemove(UUID.randomUUID(), UUID.randomUUID(), command) @@ -116,6 +118,7 @@ class AddOrRemoveJarCategoryServiceSpec extends Specification { and: 'the added categories are present' assert either.get().size() == 1 assert either.get().getFirst().getCategoryId() == new CategoryIdentifier(UUID.fromString(jarCategoryId)) + assert either.get().getFirst().getCategoryName() == categoryName assert either.get().getFirst().getJar() == newSampleJar() assert either.get().getFirst().getCreationTimestamp() != null diff --git a/server/src/test/resources/spendingplan/messaging/create-complete-plan-structure.sql b/server/src/test/resources/spendingplan/messaging/create-complete-plan-structure.sql index 1412deb..c4142b3 100644 --- a/server/src/test/resources/spendingplan/messaging/create-complete-plan-structure.sql +++ b/server/src/test/resources/spendingplan/messaging/create-complete-plan-structure.sql @@ -41,16 +41,20 @@ VALUES ('e2709aa2-7907-4f78-98b6-0f36a0c1b5ca', INSERT INTO server.spending_jar_expense_category(id, jar_id, category_id, + category_name, creation_timestamp) VALUES (1, 'e2709aa2-7907-4f78-98b6-0f36a0c1b5ca', 'e2709aa2-7907-4f78-98b6-0f36a0c1b5ca', + 'Clothing', '2024-03-23T10:00:04.224870Z'), (2, 'a6993312-2e45-43e4-b965-9edc88da7a00', 'ee0a4cdc-84f0-4f81-8aea-224dad4915e7', + 'Clothing', '2024-03-23T10:00:04.224870Z'), (3, 'a6993312-2e45-43e4-b965-9edc88da7a00', '8a366e74-b4e3-4e64-a2a6-dce273ce332a', + 'Clothing', '2024-03-23T10:00:04.224870Z'); diff --git a/server/src/test/resources/spendingplan/web/jar_expense_category-create.sql b/server/src/test/resources/spendingplan/web/jar_expense_category-create.sql index 0e972b7..d600655 100644 --- a/server/src/test/resources/spendingplan/web/jar_expense_category-create.sql +++ b/server/src/test/resources/spendingplan/web/jar_expense_category-create.sql @@ -1,12 +1,15 @@ INSERT INTO server.spending_jar_expense_category(id, jar_id, category_id, + category_name, creation_timestamp) VALUES (1, 'e2709aa2-7907-4f78-98b6-0f36a0c1b5ca', 'e2709aa2-7907-4f78-98b6-0f36a0c1b5ca', + 'Clothing', '2024-03-23T10:00:04.224870Z'), (2, 'e2709aa2-7907-4f78-98b6-0f36a0c1b5ca', 'ee0a4cdc-84f0-4f81-8aea-224dad4915e7', + 'Clothing', '2024-03-23T10:00:04.224870Z');