-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add modification (add/remove) expense categories to plan jar
- Loading branch information
Showing
18 changed files
with
633 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
server/src/main/java/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package io.myfinbox.spendingplan.application; | ||
|
||
import io.myfinbox.shared.Failure; | ||
import io.myfinbox.spendingplan.domain.*; | ||
import io.vavr.control.Either; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
import static io.myfinbox.spendingplan.domain.JarExpenseCategory.CategoryIdentifier; | ||
import static io.myfinbox.spendingplan.domain.Plan.PlanIdentifier; | ||
import static java.util.Objects.isNull; | ||
|
||
@Service | ||
@Transactional | ||
@RequiredArgsConstructor | ||
class AddOrRemoveJarCategoryService implements AddOrRemoveJarCategoryUseCase { | ||
|
||
static final String VALIDATION_CREATE_FAILURE_MESSAGE = "Failed to validate the request to add or remove categories to plan jar."; | ||
static final String PLAN_NOT_FOUND_MESSAGE = "Spending plan was not found."; | ||
static final String JAR_NOT_FOUND_MESSAGE = "Spending jar was not found."; | ||
static final String PLAN_JAR_NOT_FOUND_MESSAGE = "Spending plan jar was not found."; | ||
|
||
private final CategoriesJarCommandValidator validator = new CategoriesJarCommandValidator(); | ||
|
||
private final Jars jars; | ||
private final JarExpenseCategories jarExpenseCategories; | ||
|
||
@Override | ||
public Either<Failure, List<JarExpenseCategory>> addOrRemove(UUID planId, UUID jarId, JarCategoriesCommand command) { | ||
var either = validate(planId, jarId, command); | ||
if (either.isLeft()) { | ||
return either; | ||
} | ||
|
||
// require plan jar to exist | ||
var jar = jars.findByIdAndPlanId(new JarIdentifier(jarId), new PlanIdentifier(planId)); | ||
if (jar.isEmpty()) { | ||
return Either.left(Failure.ofNotFound(PLAN_JAR_NOT_FOUND_MESSAGE)); | ||
} | ||
|
||
// select unchecked and delete | ||
command.categories().stream() | ||
.filter(jarCategoryToAddOrRemove -> !jarCategoryToAddOrRemove.toAdd()) | ||
.map(JarCategoryToAddOrRemove::categoryId) | ||
.forEach(category -> | ||
jarExpenseCategories.deleteByJarIdAndCategoryId(new JarIdentifier(jarId), new CategoryIdentifier(category))); | ||
|
||
// select checked and create if not exists | ||
var toCreateCategories = filterToCreate(jar.get(), command); | ||
|
||
jarExpenseCategories.saveAll(toCreateCategories); | ||
|
||
return Either.right(toCreateCategories); | ||
} | ||
|
||
private Either<Failure, List<JarExpenseCategory>> validate(UUID planId, UUID jarId, JarCategoriesCommand command) { | ||
// schema validation | ||
var failure = validator.validate(command.categories()); | ||
if (failure.isInvalid()) { | ||
return Either.left(Failure.ofValidation(VALIDATION_CREATE_FAILURE_MESSAGE, List.of(failure.getError()))); | ||
} | ||
|
||
// validate non null plan id | ||
if (isNull(planId)) { | ||
return Either.left(Failure.ofNotFound(PLAN_NOT_FOUND_MESSAGE)); | ||
} | ||
|
||
// validate non null jar id | ||
if (isNull(jarId)) { | ||
return Either.left(Failure.ofNotFound(JAR_NOT_FOUND_MESSAGE)); | ||
} | ||
|
||
return Either.right(null); | ||
} | ||
|
||
private List<JarExpenseCategory> filterToCreate(Jar jar, JarCategoriesCommand command) { | ||
return command.categories().stream() | ||
.filter(JarCategoryToAddOrRemove::toAdd) | ||
.map(JarCategoryToAddOrRemove::categoryId) | ||
.filter(category -> !existsByJarIdAndCategoryId(jar.getId(), category)) | ||
.map(category -> JarExpenseCategory.builder() | ||
.categoryId(new CategoryIdentifier(category)) | ||
.jar(jar) | ||
.build()) | ||
.toList(); | ||
} | ||
|
||
private boolean existsByJarIdAndCategoryId(JarIdentifier jarId, UUID category) { | ||
return jarExpenseCategories.existsByJarIdAndCategoryId(jarId, new CategoryIdentifier(category)); | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
server/src/main/java/io/myfinbox/spendingplan/application/AddOrRemoveJarCategoryUseCase.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package io.myfinbox.spendingplan.application; | ||
|
||
import io.myfinbox.shared.Failure; | ||
import io.myfinbox.spendingplan.domain.JarExpenseCategory; | ||
import io.vavr.control.Either; | ||
|
||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
import static java.util.Objects.isNull; | ||
|
||
/** | ||
* Interface representing a use case for adding or removing categories to/from a plan jar. | ||
*/ | ||
public interface AddOrRemoveJarCategoryUseCase { | ||
|
||
/** | ||
* Adds or removes categories to/from a specified jar within a spending plan jar. | ||
* | ||
* @param planId The ID of the spending plan containing the jar. | ||
* @param jarId The ID of the jar to which categories will be added or removed. | ||
* @param command The command containing information about the categories to add or remove. | ||
* @return An {@link Either} containing either a {@link Failure} if the operation fails | ||
* or a list of added {@link JarExpenseCategory} after the operation. | ||
*/ | ||
Either<Failure, List<JarExpenseCategory>> addOrRemove(UUID planId, UUID jarId, JarCategoriesCommand command); | ||
|
||
record JarCategoriesCommand(List<JarCategoryToAddOrRemove> categories) { | ||
|
||
public static final String CATEGORIES_JAR_FIELD = "categories"; | ||
} | ||
|
||
record JarCategoryToAddOrRemove(UUID categoryId, Boolean toAdd) { | ||
|
||
/** | ||
* Gets whether the category is checked or not. | ||
* Null is treated as true. | ||
* | ||
* @return True if the category is checked, false otherwise. | ||
*/ | ||
public Boolean toAdd() { // null treated as true | ||
return isNull(toAdd) || toAdd; | ||
} | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
server/src/main/java/io/myfinbox/spendingplan/application/CategoriesJarCommandValidator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package io.myfinbox.spendingplan.application; | ||
|
||
import io.myfinbox.shared.Failure; | ||
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; | ||
import static io.myfinbox.spendingplan.application.AddOrRemoveJarCategoryUseCase.JarCategoriesCommand.CATEGORIES_JAR_FIELD; | ||
import static io.myfinbox.spendingplan.application.AddOrRemoveJarCategoryUseCase.JarCategoryToAddOrRemove; | ||
import static io.vavr.API.Invalid; | ||
import static io.vavr.API.Valid; | ||
import static java.util.Objects.isNull; | ||
|
||
final class CategoriesJarCommandValidator { | ||
|
||
Validation<FieldViolation, List<JarCategoryToAddOrRemove>> validate(List<JarCategoryToAddOrRemove> categories) { | ||
if (isNull(categories) || categories.isEmpty()) { | ||
return Invalid(Failure.FieldViolation.builder() | ||
.field(CATEGORIES_JAR_FIELD) | ||
.message("At least one category must be provided.") | ||
.rejectedValue(categories) | ||
.build()); | ||
} | ||
|
||
var anyNull = categories.stream() | ||
.map(JarCategoryToAddOrRemove::categoryId) | ||
.anyMatch(Objects::isNull); | ||
|
||
if (anyNull) { | ||
return Invalid(Failure.FieldViolation.builder() | ||
.field(CATEGORIES_JAR_FIELD) | ||
.message("Null categoryId not allowed.") | ||
.rejectedValue(categories) | ||
.build()); | ||
} | ||
|
||
var categoryCount = categories.stream() | ||
.collect(Collectors.groupingBy(JarCategoryToAddOrRemove::categoryId)); | ||
|
||
var anyDuplicate = categoryCount.values().stream() | ||
.map(List::size) | ||
.anyMatch(size -> size > 1); | ||
|
||
if (anyDuplicate) { | ||
return Invalid(Failure.FieldViolation.builder() | ||
.field(CATEGORIES_JAR_FIELD) | ||
.message("Duplicate category ids provided.") | ||
.rejectedValue(categories) | ||
.build()); | ||
} | ||
|
||
return Valid(categories); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.