From c83c628818155eb8c541a6fd5c3f8192968d4f88 Mon Sep 17 00:00:00 2001 From: Zikani Nyirenda Mwase Date: Tue, 2 Aug 2022 18:43:48 +0200 Subject: [PATCH] feat: Initial design of Topups and some bug fixes Squashed commit of the following: commit 322b360f31703ba9ba13e8f79a90404aacdf1d8f Author: Zikani Nyirenda Mwase Date: Tue Aug 2 10:38:56 2022 +0200 fix: Set representative phone number, add yesOrNo for boolean fields. commit 38af5fcaabf0f9184fc0da985c180123f9cafaef Author: Zikani Nyirenda Mwase Date: Mon Aug 1 16:27:36 2022 +0200 feat: Redirect to index onn failed pre-conditions Added a basic function for redirecting when a precondition has failed commit ba7559c8e60b15c63765611043076ad16a151b60 Author: Zikani Nyirenda Mwase Date: Mon Aug 1 15:31:45 2022 +0200 fix: Fix saving of Phone number for Transfer Agencies. commit f9d3d0b78d90620a7ddd1078e558549b6621e1f9 Author: Zikani Nyirenda Mwase Date: Mon Aug 1 15:31:18 2022 +0200 chore: Moved functions from util/form_extras to utils/form Removed form_extras.peb commit 91a453cf7976b6fb4f1183a6249e573585207bd3 Author: Zikani Nyirenda Mwase Date: Mon Aug 1 15:30:22 2022 +0200 feat: Add TopUps calculation to TransferCalculator Added calculation of the transfer topups to the calculator except for the Admin Fees figure which has to be designed/approached properly commit 6015763d49a8fa59f12b5c3ef56cddb71305c487 Author: Zikani Nyirenda Mwase Date: Wed Jul 27 10:40:16 2022 +0200 chore: Re-order elements on navigation commit 32c3f804ee4db2a9b96f7888ffbab8f5c9479dfa Author: Zikani Nyirenda Mwase Date: Tue Jul 26 16:41:44 2022 +0200 feat: Implement basic create functionality for TopUps commit 9b34a6b7a16b892f3f8cbf30696e00f132b44931 Author: Zikani Nyirenda Mwase Date: Mon Jul 25 16:54:55 2022 +0200 feat: Add topups migration, entity and base views commit cced355f7a15e6f4d4e71d8cedd8b49c11c15f18 Author: Zikani Nyirenda Mwase Date: Mon Jul 25 16:52:45 2022 +0200 chore: Make class public for use elsewhere commit 2ff4d1edcb0f5e6c0e105ff26c6b8f7a2705dad4 Author: Zikani Nyirenda Mwase Date: Mon Jul 25 16:51:49 2022 +0200 fix: Show error messages on new forms --- .../api/transfers/topups/TopUpController.java | 36 +++ .../java/org/cga/sctp/schools/School.java | 9 +- .../UbrParameterValueConverter.java | 2 +- .../java/org/cga/sctp/transfers/Transfer.java | 2 +- .../sctp/transfers/TransferCalculator.java | 65 ++-- .../sctp/transfers/topups/NewTopUpForm.java | 208 +++++++++++++ .../org/cga/sctp/transfers/topups/TopUp.java | 277 ++++++++++++++++++ .../topups/TopUpHouseholdStatus.java | 65 ++++ .../transfers/topups/TopUpRepository.java | 45 +++ .../sctp/transfers/topups/TopUpService.java | 51 ++++ .../transfers/topups/TopUpServiceImpl.java | 97 ++++++ .../cga/sctp/transfers/topups/TopUpType.java | 70 +++++ .../V1.62.1658365166461__Add_topups_table.sql | 24 ++ .../transfers/TransferCalculatorTest.java | 31 +- .../org/cga/sctp/mis/core/BaseController.java | 7 + .../core/templating/SelectOptionConfigs.java | 24 ++ .../agencies/TransferAgenciesController.java | 2 + .../agencies/TransferAgencyForm.java | 3 +- .../topups/TransferTopUpsController.java | 116 +++++++- .../src/main/resources/templates/navbar.peb | 6 +- .../transfers/parameters/education/new.peb | 1 + .../transfers/parameters/households/new.peb | 1 + .../templates/transfers/parameters/new.peb | 1 + .../templates/transfers/topups/edit.peb | 0 .../templates/transfers/topups/list.peb | 66 +++++ .../templates/transfers/topups/new.peb | 61 ++++ .../templates/transfers/topups/view.peb | 0 .../main/resources/templates/utils/form.peb | 33 +++ 28 files changed, 1274 insertions(+), 29 deletions(-) create mode 100644 sctp-api/src/main/java/org/cga/sctp/api/transfers/topups/TopUpController.java create mode 100644 sctp-core/src/main/java/org/cga/sctp/transfers/topups/NewTopUpForm.java create mode 100644 sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUp.java create mode 100644 sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpHouseholdStatus.java create mode 100644 sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpRepository.java create mode 100644 sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpService.java create mode 100644 sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpServiceImpl.java create mode 100644 sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpType.java create mode 100644 sctp-core/src/main/resources/db/migration/V1.62.1658365166461__Add_topups_table.sql create mode 100644 sctp-mis/src/main/resources/templates/transfers/topups/edit.peb create mode 100644 sctp-mis/src/main/resources/templates/transfers/topups/list.peb create mode 100644 sctp-mis/src/main/resources/templates/transfers/topups/new.peb create mode 100644 sctp-mis/src/main/resources/templates/transfers/topups/view.peb diff --git a/sctp-api/src/main/java/org/cga/sctp/api/transfers/topups/TopUpController.java b/sctp-api/src/main/java/org/cga/sctp/api/transfers/topups/TopUpController.java new file mode 100644 index 00000000..93553494 --- /dev/null +++ b/sctp-api/src/main/java/org/cga/sctp/api/transfers/topups/TopUpController.java @@ -0,0 +1,36 @@ +/* + * BSD 3-Clause License + * + * Copyright (c) 2022, CGATechnologies + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.cga.sctp.api.transfers.topups; + +public class TopUpController { +} diff --git a/sctp-core/src/main/java/org/cga/sctp/schools/School.java b/sctp-core/src/main/java/org/cga/sctp/schools/School.java index d06a452b..8baf95d3 100644 --- a/sctp-core/src/main/java/org/cga/sctp/schools/School.java +++ b/sctp-core/src/main/java/org/cga/sctp/schools/School.java @@ -32,11 +32,12 @@ package org.cga.sctp.schools; -import org.cga.sctp.schools.educationzone.EducationZone; import org.cga.sctp.targeting.importation.converters.EducationLevelParameterValueConverter; import org.cga.sctp.targeting.importation.parameters.EducationLevel; import javax.persistence.*; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; import java.time.LocalDateTime; @Entity @@ -47,15 +48,19 @@ public class School { @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; + @NotEmpty(message = "Name cannot be empty or null") @Column private String name; + @NotEmpty(message = "School code cannot be null") @Column private String code; + @NotNull(message = "Education Level must be specified") @Convert(converter = EducationLevelParameterValueConverter.class) private EducationLevel educationLevel; + @NotNull(message = "Education Zone ID must be specified") @Column(name="education_zone") private Long educationZoneId; @@ -68,9 +73,11 @@ public class School { @Column private Boolean active; + @NotNull(message = "Created At cannot be null") @Column private LocalDateTime createdAt; + @NotNull(message = "Modified At cannot be null") @Column private LocalDateTime modifiedAt; diff --git a/sctp-core/src/main/java/org/cga/sctp/targeting/importation/converters/UbrParameterValueConverter.java b/sctp-core/src/main/java/org/cga/sctp/targeting/importation/converters/UbrParameterValueConverter.java index 55dab5f2..204f41b4 100644 --- a/sctp-core/src/main/java/org/cga/sctp/targeting/importation/converters/UbrParameterValueConverter.java +++ b/sctp-core/src/main/java/org/cga/sctp/targeting/importation/converters/UbrParameterValueConverter.java @@ -36,7 +36,7 @@ import javax.persistence.AttributeConverter; -abstract class UbrParameterValueConverter implements AttributeConverter { +public abstract class UbrParameterValueConverter implements AttributeConverter { private final UbrParameterValue[] values; public UbrParameterValueConverter(UbrParameterValue[] values) { diff --git a/sctp-core/src/main/java/org/cga/sctp/transfers/Transfer.java b/sctp-core/src/main/java/org/cga/sctp/transfers/Transfer.java index 3173890f..be34064a 100644 --- a/sctp-core/src/main/java/org/cga/sctp/transfers/Transfer.java +++ b/sctp-core/src/main/java/org/cga/sctp/transfers/Transfer.java @@ -522,6 +522,6 @@ public void setReviewedBy(Long reviewedBy) { // TODO: Make this a property in the database ? or pre-compute public Long getTotalAmountToTransfer() { - return this.basicSubsidyAmount + this.secondaryBonusAmount /* + TODO: this.primaryBonusAmount */ + this.primaryIncentiveAmount; + return this.basicSubsidyAmount + this.secondaryBonusAmount + this.primaryBonusAmount + this.primaryIncentiveAmount + this.topupAmount; } } diff --git a/sctp-core/src/main/java/org/cga/sctp/transfers/TransferCalculator.java b/sctp-core/src/main/java/org/cga/sctp/transfers/TransferCalculator.java index 3875560c..a16521b0 100644 --- a/sctp-core/src/main/java/org/cga/sctp/transfers/TransferCalculator.java +++ b/sctp-core/src/main/java/org/cga/sctp/transfers/TransferCalculator.java @@ -37,8 +37,8 @@ import org.cga.sctp.transfers.parameters.EducationTransferParameter; import org.cga.sctp.transfers.parameters.HouseholdParameterCondition; import org.cga.sctp.transfers.parameters.HouseholdTransferParameter; -import org.cga.sctp.transfers.parameters.TransferParametersService; import org.cga.sctp.transfers.periods.TransferPeriod; +import org.cga.sctp.transfers.topups.TopUp; import java.util.List; import java.util.Optional; @@ -47,9 +47,20 @@ public class TransferCalculator { private List educationTransferParameters; private List householdTransferParameters; - public TransferCalculator(List educationTransferParameters, List householdTransferParameters) { + private List topUps; + + /** + * + * @param householdTransferParameters Parameters + * @param educationTransferParameters Parameters for education + * @param topUps topups that are applicable to the households to calculate transfers for + */ + public TransferCalculator(List householdTransferParameters, + List educationTransferParameters, + List topUps) { this.educationTransferParameters = educationTransferParameters; this.householdTransferParameters = householdTransferParameters; + this.topUps = topUps; } @@ -124,6 +135,40 @@ public Long determineAmountByHouseholdSize(int householdSize, List topUpList) { + double totalTopupAmout = 0.0; + for (var topup : topUpList) { + totalTopupAmout += switch (topup.getTopupType()) { + case FIXED_AMOUNT -> topup.getAmount(); + case EQUIVALENT_BENEFICIARY_AMOUNT -> monthlyAmount; + case PERCENTAGE_OF_RECIPIENT_AMOUNT -> (monthlyAmount * topup.getPercentage())/100d; + default -> topup.getAmount(); + }; + } + // TODO: avoid conversion to long, we lose fidelity here.. + return (long) totalTopupAmout; + } + + protected void calculateTransferAmount(Transfer transfer,Location location, TransferPeriod transferPeriod) { + long monthlyAmount = 0; + final long basicAmount = determineAmountByHouseholdSize(transfer.getHouseholdMemberCount(), householdTransferParameters), + secondaryBonus = transfer.getSecondaryChildrenCount() * getSecondaryBonusAmount(educationTransferParameters), + primaryBonus = transfer.getPrimaryChildrenCount() * getPrimaryBonusAmount(educationTransferParameters), + primaryIncentive = transfer.getPrimaryIncentiveChildrenCount() * getPrimaryIncentiveAmount(); + + transfer.setAmountDisbursed(0L); + transfer.setNumberOfMonths(transferPeriod.countNoOfMonths()); + transfer.setBasicSubsidyAmount(basicAmount); + transfer.setPrimaryIncentiveAmount(primaryIncentive); + transfer.setPrimaryBonusAmount(primaryBonus); + transfer.setSecondaryBonusAmount(secondaryBonus); + // TODO: Should we set this or calculate upon request + monthlyAmount = basicAmount + primaryBonus + secondaryBonus + primaryIncentive; + + // Calculate topups + transfer.setTopupAmount(calculateTopUpAmount(transfer, monthlyAmount, topUps)); + } + /** * Calculates transfers for all the households in the transfer period for a given location.. * @param transferPeriod @@ -135,21 +180,7 @@ public void calculateTransfersUpdate(Location location, TransferPeriod transferP if (transfer.getTransferPeriodId() != transferPeriod.getId()) { continue; } - basicAmount = determineAmountByHouseholdSize(transfer.getHouseholdMemberCount(), householdTransferParameters); - secondaryBonus = transfer.getSecondaryChildrenCount() * getSecondaryBonusAmount(educationTransferParameters); - primaryBonus = transfer.getPrimaryChildrenCount() * getPrimaryBonusAmount(educationTransferParameters); - // TODO: store the number of children that need/want incentives for primary school... - primaryIncentive = transfer.getPrimaryIncentiveChildrenCount() * getPrimaryIncentiveAmount(); - - transfer.setNumberOfMonths(transferPeriod.countNoOfMonths()); - transfer.setAmountDisbursed(0L); - transfer.setTopupAmount(0L); - transfer.setBasicSubsidyAmount(basicAmount); - transfer.setPrimaryIncentiveAmount(primaryIncentive); - // TODO: transfer.setPrimaryBonusAmount(primaryIncentive); - transfer.setSecondaryBonusAmount(secondaryBonus); - // TODO: Should we set this or calculate upon request - monthlyAmount = basicAmount + primaryBonus + secondaryBonus + primaryIncentive; + this.calculateTransferAmount(transfer, location, transferPeriod); } } } diff --git a/sctp-core/src/main/java/org/cga/sctp/transfers/topups/NewTopUpForm.java b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/NewTopUpForm.java new file mode 100644 index 00000000..6704a235 --- /dev/null +++ b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/NewTopUpForm.java @@ -0,0 +1,208 @@ +/* + * BSD 3-Clause License + * + * Copyright (c) 2022, CGATechnologies + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.cga.sctp.transfers.topups; + +import org.cga.sctp.location.LocationType; +import org.springframework.lang.Nullable; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +public class NewTopUpForm { + @NotNull + @NotEmpty(message = "name cannot be empty") + private String name; + + @NotNull(message = "Program must be specified") + private Long programId; + + @NotNull(message = "Sponsor / Funding institution must be specified") + private Long funderId; + + @NotNull(message = "Location(s) must be specified") + private Long locationId; + + @NotNull(message = "Location Type must be specified") + private LocationType locationType; + + @Nullable + private Double percentage; + + @NotNull(message = "Type of TopUp must be specified") + private TopUpType topupType; + + @NotNull(message = "Please specify household status to get topups: whether both, recertified or non-recertified") + private TopUpHouseholdStatus householdStatus; + + @NotNull + private boolean active; + + private Long amount; + + @NotNull(message = "Specify whether the topup is categorical or not") + private boolean categorical; + + private Long categoricalTargetingCriteriaId; + + @NotNull(message = "Please specify whether amount will be discounted from the program Funds") + private boolean discountedFromFunds; + + private Long userId; + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getFunderId() { + return funderId; + } + + public void setFunderId(Long funderId) { + this.funderId = funderId; + } + + public Long getProgramId() { + return programId; + } + + public void setProgramId(Long programId) { + this.programId = programId; + } + + public Long getLocationId() { + return locationId; + } + + public void setLocationId(Long locationId) { + this.locationId = locationId; + } + + public LocationType getLocationType() { + return locationType; + } + + public void setLocationType(LocationType locationType) { + this.locationType = locationType; + } + + public double getPercentage() { + return percentage; + } + + public void setPercentage(double percentage) { + this.percentage = percentage; + } + + public TopUpType getTopupType() { + return topupType; + } + + public void setTopupType(TopUpType topupType) { + this.topupType = topupType; + } + + public TopUpHouseholdStatus getHouseholdStatus() { + return householdStatus; + } + + public void setHouseholdStatus(TopUpHouseholdStatus householdStatus) { + this.householdStatus = householdStatus; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public Long getAmount() { + return amount; + } + + public void setAmount(Long amount) { + this.amount = amount; + } + + public boolean isCategorical() { + return categorical; + } + + public void setCategorical(boolean categorical) { + this.categorical = categorical; + } + + public Long getCategoricalTargetingCriteriaId() { + return categoricalTargetingCriteriaId; + } + + public void setCategoricalTargetingCriteriaId(Long categoricalTargetingCriteriaId) { + this.categoricalTargetingCriteriaId = categoricalTargetingCriteriaId; + } + + public boolean isDiscountedFromFunds() { + return discountedFromFunds; + } + + public void setDiscountedFromFunds(boolean discountedFromFunds) { + this.discountedFromFunds = discountedFromFunds; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + + public static class Validator { + public boolean isValid(NewTopUpForm form) { + if (form.getTopupType() == TopUpType.PERCENTAGE_OF_RECIPIENT_AMOUNT) { + if (form.getPercentage() < 0.00 || form.getPercentage() > 100.00) { + // invalid percentage range + return false; + } + } + + return true; + } + } +} diff --git a/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUp.java b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUp.java new file mode 100644 index 00000000..dbd81e2c --- /dev/null +++ b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUp.java @@ -0,0 +1,277 @@ +/* + * BSD 3-Clause License + * + * Copyright (c) 2022, CGATechnologies + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.cga.sctp.transfers.topups; + +import org.cga.sctp.location.LocationType; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name="transfer_topups") +public class TopUp { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // id BIGINT(19) PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT 'Primary key', + + @Column(name = "name") + private String name; // name VARCHAR(90) NULL DEFAULT NULL COMMENT 'Name of topup' COLLATE 'utf8_unicode_ci', + + @Column(name = "program_id") + private Long programId; // Program for the topup + + @Column(name = "funder_id") + private Long funderId; // funder_id BIGINT(19) NOT NULL COMMENT 'Funding institution for this topup', + + @Column(name = "location_id") + private Long locationId; // location_id INT(10) NOT NULL COMMENT 'FOREIGN KEY Table geolocation Field geo_id, District', + + @Column(name = "location_type") + @Enumerated(EnumType.STRING) + private LocationType locationType; // location_type INT(10) NOT NULL COMMENT 'FOREIGN KEY Table item Field ite_id, Level', + + @Column(name = "is_discounted_from_funds") + private boolean isDiscountedFromFunds; // is_discounted_from_funds TINYINT(1) NULL DEFAULT NULL COMMENT 'The top-up arrears are discounted from the SCTP funds to be requested', + + @Column(name = "is_categorical") + private boolean isCategorical; // is_categorical TINYINT(1) NOT NULL DEFAULT '0' COMMENT 'Whether topup is categorical or not', + + @Column(name = "is_active") + private boolean isActive; // is_active TINYINT(1) NOT NULL DEFAULT '0' COMMENT 'Active or not 1 = yes, 0 = no', + + @Column(name = "is_executed") + private boolean isExecuted; // is_executed TINYINT(1) NOT NULL DEFAULT '0' COMMENT 'Executed or not 1 = yes, 0 = no', + + @Column(name = "topup_type") + @Convert(converter = TopUpType.Converter.class) + private TopUpType topupType; // topup_type TINYINT(1) NOT NULL DEFAULT '0' COMMENT 'Topup type, See TopupType enumeration', + + @Column + @Convert(converter = TopUpHouseholdStatus.Converter.class) + private TopUpHouseholdStatus householdStatus; + + @Column(name = "percentage") + private double percentage; // percentage DOUBLE COMMENT 'For percentage based topups, the percentage to use for calculation', + + @Column(name = "categorical_targeting_criteria_id") + private Long categoricalTargetingCriteriaId; // categorical_targeting_criteria_id BIGINT(19) NULL COMMENT 'For categorical topups, the criteria to use.', + + @Column(name = "amount") + private Long amount; // amount BIGINT(19) NULL DEFAULT NULL COMMENT 'Amount to topup', + + @Column(name = "amount_projected") + private Long amountProjected; // amount_projected BIGINT(19) NULL DEFAULT NULL COMMENT 'Amount projected', + + @Column(name = "amount_executed") + private Long amountExecuted; // amount_executed BIGINT(19) NULL DEFAULT NULL COMMENT 'Amount executed i.e. disbursed', + + @Column(name = "created_by") + private Long createdBy; // created_by BIGINT(19) NOT NULL COMMENT 'User who created the topup', + + @Column(name = "updated_by") + private Long updatedBy; // updated_by BIGINT(19) NOT NULL COMMENT 'User who updated the topup', + + @Column(name = "created_at") + private LocalDateTime createdAt; // created_at timestamp not null, + + @Column(name = "updated_at") + private LocalDateTime updatedAt; // updated_at timestamp not null + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getFunderId() { + return funderId; + } + + public void setFunderId(Long funderId) { + this.funderId = funderId; + } + + public Long getProgramId() { + return programId; + } + + public void setProgramId(Long programId) { + this.programId = programId; + } + + public Long getLocationId() { + return locationId; + } + + public void setLocationId(Long locationId) { + this.locationId = locationId; + } + + public LocationType getLocationType() { + return locationType; + } + + public void setLocationType(LocationType locationType) { + this.locationType = locationType; + } + + public boolean isDiscountedFromFunds() { + return isDiscountedFromFunds; + } + + public void setDiscountedFromFunds(boolean discountedFromFunds) { + isDiscountedFromFunds = discountedFromFunds; + } + + public boolean isCategorical() { + return isCategorical; + } + + public void setCategorical(boolean categorical) { + isCategorical = categorical; + } + + public boolean isActive() { + return isActive; + } + + public void setActive(boolean active) { + isActive = active; + } + + public boolean isExecuted() { + return isExecuted; + } + + public void setExecuted(boolean executed) { + isExecuted = executed; + } + + public TopUpType getTopupType() { + return topupType; + } + + public void setTopupType(TopUpType topupType) { + this.topupType = topupType; + } + + public TopUpHouseholdStatus getHouseholdStatus() { + return householdStatus; + } + + public void setHouseholdStatus(TopUpHouseholdStatus householdStatus) { + this.householdStatus = householdStatus; + } + + public double getPercentage() { + return percentage; + } + + public void setPercentage(double percentage) { + this.percentage = percentage; + } + + public Long getCategoricalTargetingCriteriaId() { + return categoricalTargetingCriteriaId; + } + + public void setCategoricalTargetingCriteriaId(Long categoricalTargetingCriteriaId) { + this.categoricalTargetingCriteriaId = categoricalTargetingCriteriaId; + } + + public Long getAmount() { + return amount; + } + + public void setAmount(Long amount) { + this.amount = amount; + } + + public Long getAmountProjected() { + return amountProjected; + } + + public void setAmountProjected(Long amountProjected) { + this.amountProjected = amountProjected; + } + + public Long getAmountExecuted() { + return amountExecuted; + } + + public void setAmountExecuted(Long amountExecuted) { + this.amountExecuted = amountExecuted; + } + + public Long getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(Long createdBy) { + this.createdBy = createdBy; + } + + public Long getUpdatedBy() { + return updatedBy; + } + + public void setUpdatedBy(Long updatedBy) { + this.updatedBy = updatedBy; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpHouseholdStatus.java b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpHouseholdStatus.java new file mode 100644 index 00000000..f2517186 --- /dev/null +++ b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpHouseholdStatus.java @@ -0,0 +1,65 @@ +/* + * BSD 3-Clause License + * + * Copyright (c) 2022, CGATechnologies + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.cga.sctp.transfers.topups; + +import org.cga.sctp.targeting.importation.converters.UbrParameterValueConverter; +import org.cga.sctp.targeting.importation.parameters.UbrParameterValue; + +public enum TopUpHouseholdStatus implements UbrParameterValue { + BOTH(0,"Both"), + RECERTIFIED(15, "Recertified"), + NON_RECERTIFIED(112, "Non-Recertified"); + + int code; + String description; + + TopUpHouseholdStatus(int code, String description) { + this.code = code; + this.description = description; + } + + @Override + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public static class Converter extends UbrParameterValueConverter { + public Converter() { + super(TopUpHouseholdStatus.values()); + } + } +} diff --git a/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpRepository.java b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpRepository.java new file mode 100644 index 00000000..74c287ec --- /dev/null +++ b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpRepository.java @@ -0,0 +1,45 @@ +/* + * BSD 3-Clause License + * + * Copyright (c) 2022, CGATechnologies + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.cga.sctp.transfers.topups; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface TopUpRepository extends JpaRepository { + @Query + List findAllByIsActive(boolean value); +} diff --git a/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpService.java b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpService.java new file mode 100644 index 00000000..9069636f --- /dev/null +++ b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpService.java @@ -0,0 +1,51 @@ +/* + * BSD 3-Clause License + * + * Copyright (c) 2022, CGATechnologies + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.cga.sctp.transfers.topups; + +import org.cga.sctp.location.Location; + +import java.util.List; +import java.util.Optional; + +public interface TopUpService { + + Optional newTopup(NewTopUpForm params); + + List fetchAllActive(Optional location); + + List fetchAllExecuted(Optional location); + + void markAsExecuted(TopUp topUp, Long amount); + + List findAllActive(); +} diff --git a/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpServiceImpl.java b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpServiceImpl.java new file mode 100644 index 00000000..8cc3118a --- /dev/null +++ b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpServiceImpl.java @@ -0,0 +1,97 @@ +/* + * BSD 3-Clause License + * + * Copyright (c) 2022, CGATechnologies + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.cga.sctp.transfers.topups; + +import org.cga.sctp.location.Location; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Service +public class TopUpServiceImpl implements TopUpService { + @Autowired + private TopUpRepository topUpRepository; + + @Override + public Optional newTopup(NewTopUpForm params) { + TopUp topUp = new TopUp(); + topUp.setProgramId(params.getProgramId()); + topUp.setActive(params.isActive()); + topUp.setAmount(params.getAmount()); + topUp.setAmountExecuted(0L); + topUp.setAmountProjected(0L); + topUp.setCategorical(params.isCategorical()); + topUp.setCategoricalTargetingCriteriaId(params.getCategoricalTargetingCriteriaId()); + + topUp.setDiscountedFromFunds(params.isDiscountedFromFunds()); + topUp.setExecuted(false); + topUp.setFunderId(params.getFunderId()); + topUp.setHouseholdStatus(params.getHouseholdStatus()); + topUp.setLocationId(params.getLocationId()); + topUp.setLocationType(params.getLocationType()); + topUp.setName(params.getName()); + topUp.setPercentage(params.getPercentage()); + topUp.setTopupType(params.getTopupType()); + + LocalDateTime now = LocalDateTime.now(); + topUp.setCreatedBy(params.getUserId()); + topUp.setUpdatedBy(params.getUserId()); + topUp.setCreatedAt(now); + topUp.setUpdatedAt(now); + + return Optional.ofNullable(topUpRepository.save(topUp)); + } + + @Override + public List fetchAllActive(Optional location) { + throw new RuntimeException("not implemented"); + } + + @Override + public List fetchAllExecuted(Optional location) { + throw new RuntimeException("not implemented"); + } + + @Override + public void markAsExecuted(TopUp topUp, Long amount) { + throw new RuntimeException("not implemented"); + } + + @Override + public List findAllActive() { + return topUpRepository.findAllByIsActive(true); + } +} diff --git a/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpType.java b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpType.java new file mode 100644 index 00000000..768a410a --- /dev/null +++ b/sctp-core/src/main/java/org/cga/sctp/transfers/topups/TopUpType.java @@ -0,0 +1,70 @@ +/* + * BSD 3-Clause License + * + * Copyright (c) 2022, CGATechnologies + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.cga.sctp.transfers.topups; + +import org.cga.sctp.targeting.importation.converters.UbrParameterValueConverter; +import org.cga.sctp.targeting.importation.parameters.UbrParameterValue; + +import javax.persistence.Converter; + +/** + * TopUp Type determines how the amount to be disbursed to a household is calculated. + */ +public enum TopUpType implements UbrParameterValue { + FIXED_AMOUNT(1, "Fixed Amount"), + PERCENTAGE_OF_RECIPIENT_AMOUNT(2, "% of Recipient Amount"), + EQUIVALENT_BENEFICIARY_AMOUNT(3, "Current HH monthly amount"), + EPAYMENT_ADMIN_FEE_TOPUP(4, "E-Payment admin/cashout fee"); + private final int code; + private final String description; + + TopUpType(int code, String description) { + this.code = code; + this.description = description; + } + + @Override + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + static class Converter extends UbrParameterValueConverter { + public Converter() { + super(TopUpType.values()); + } + } +} diff --git a/sctp-core/src/main/resources/db/migration/V1.62.1658365166461__Add_topups_table.sql b/sctp-core/src/main/resources/db/migration/V1.62.1658365166461__Add_topups_table.sql new file mode 100644 index 00000000..d124dde1 --- /dev/null +++ b/sctp-core/src/main/resources/db/migration/V1.62.1658365166461__Add_topups_table.sql @@ -0,0 +1,24 @@ +-- Creates transfers_topups table which defines top ups for transfers in a specific program +CREATE TABLE `transfer_topups` ( + `id` BIGINT(19) PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT 'Primary key', + `name` VARCHAR(90) NULL DEFAULT NULL COMMENT 'Name of topup' COLLATE 'utf8_unicode_ci', + `funder_id` BIGINT(19) NOT NULL COMMENT 'Funding institution for this topup', + `program_id` BIGINT(19) NOT NULL COMMENT 'Program for this topup', + `location_id` INT(10) NOT NULL COMMENT 'FOREIGN KEY Table geolocation Field geo_id, District', + `location_type` INT(10) NOT NULL COMMENT 'FOREIGN KEY Table item Field ite_id, Level', + `is_discounted_from_funds` TINYINT(1) NULL DEFAULT NULL COMMENT 'The top-up arrears are discounted from the SCTP funds to be requested', + `is_categorical` TINYINT(1) NOT NULL DEFAULT '0' COMMENT 'Whether topup is categorical or not', + `is_active` TINYINT(1) NOT NULL DEFAULT '0' COMMENT 'Active or not 1 = yes, 0 = no', + `is_executed` TINYINT(1) NOT NULL DEFAULT '0' COMMENT 'Executed or not 1 = yes, 0 = no', + `topup_type` INT(3) NOT NULL DEFAULT '0' COMMENT 'Topup type, See TopupType enumeration', + `household_status` INT(3) NOT NULL DEFAULT '0' COMMENT 'Topup Household status, see enumration', + `percentage` DOUBLE COMMENT 'For percentage based topups, the percentage to use for calculation', + `categorical_targeting_criteria_id` BIGINT(19) NULL COMMENT 'For categorical topups, the criteria to use.', + `amount` BIGINT(19) NULL DEFAULT NULL COMMENT 'Amount to topup', + `amount_projected` BIGINT(19) NULL DEFAULT NULL COMMENT 'Amount projected', + `amount_executed` BIGINT(19) NULL DEFAULT NULL COMMENT 'Amount executed i.e. disbursed', + `created_by` BIGINT(19) NOT NULL COMMENT 'User who created the topup', + `updated_by` BIGINT(19) NOT NULL COMMENT 'User who updated the topup', + `created_at` timestamp not null, + `updated_at` timestamp not NULL +); diff --git a/sctp-core/src/test/java/org/cga/sctp/transfers/TransferCalculatorTest.java b/sctp-core/src/test/java/org/cga/sctp/transfers/TransferCalculatorTest.java index 3e78a720..a1f80675 100644 --- a/sctp-core/src/test/java/org/cga/sctp/transfers/TransferCalculatorTest.java +++ b/sctp-core/src/test/java/org/cga/sctp/transfers/TransferCalculatorTest.java @@ -41,6 +41,8 @@ import org.cga.sctp.transfers.parameters.HouseholdTransferParameter; import org.cga.sctp.transfers.parameters.TransferParametersService; import org.cga.sctp.transfers.periods.TransferPeriod; +import org.cga.sctp.transfers.topups.TopUp; +import org.cga.sctp.transfers.topups.TopUpType; import org.junit.jupiter.api.Test; import java.time.LocalDate; @@ -93,16 +95,39 @@ void calculateTransfersUpdate() { Transfer transfer = new Transfer(); transfer.setTransferPeriodId(transferPeriod.getId()); - transfer.setNumberOfMonths(1L); + transfer.setNumberOfMonths(transfer.getNumberOfMonths()); transfer.setHouseholdMemberCount(3); transfer.setPrimaryChildrenCount(1L); transfer.setPrimaryIncentiveChildrenCount(1L); transfer.setSecondaryChildrenCount(1L); - TransferCalculator transferCalculator = new TransferCalculator(educationTransferParameters, householdTransferParameters); + List topUps = Collections.singletonList(createBasicTopUp()); + + TransferCalculator transferCalculator = new TransferCalculator(householdTransferParameters, educationTransferParameters, topUps); transferCalculator.calculateTransfersUpdate(location, transferPeriod, Collections.singletonList(transfer)); - assertEquals(3000L + 2000L + 1000L, transfer.getTotalAmountToTransfer()); + + + assertEquals(3000, transfer.getBasicSubsidyAmount()); + assertEquals(1000, transfer.getSecondaryBonusAmount()); + assertEquals(2000, transfer.getPrimaryBonusAmount()); + assertEquals(2000, transfer.getPrimaryIncentiveAmount()); + assertEquals(4000, transfer.getTopupAmount()); + + long expectedTotal = 12000; + assertEquals(expectedTotal, transfer.getTotalAmountToTransfer()); + } + + private static TopUp createBasicTopUp() { + TopUp topUp = new TopUp(); + topUp.setName("Basic TopUp"); + topUp.setAmount(0L); + topUp.setDiscountedFromFunds(false); + topUp.setTopupType(TopUpType.PERCENTAGE_OF_RECIPIENT_AMOUNT); + topUp.setPercentage(50.00); + topUp.setExecuted(false); + topUp.setActive(true); + return topUp; } private static HouseholdTransferParameter createParam(int members, Long amount, HouseholdParameterCondition condition) { diff --git a/sctp-mis/src/main/java/org/cga/sctp/mis/core/BaseController.java b/sctp-mis/src/main/java/org/cga/sctp/mis/core/BaseController.java index b5d6c002..8c0a3bc4 100644 --- a/sctp-mis/src/main/java/org/cga/sctp/mis/core/BaseController.java +++ b/sctp-mis/src/main/java/org/cga/sctp/mis/core/BaseController.java @@ -105,4 +105,11 @@ protected final ModelAndView redirectWithDangerMessageModelAndView(String url, S setDangerFlashMessage(message, attributes); return view("redirect:" + url); } + + + protected final ModelAndView redirectOnFailedCondition(String url, String message, RedirectAttributes attributes) { + setDangerFlashMessage(message, attributes); + return view("redirect:" + url); + } + } diff --git a/sctp-mis/src/main/java/org/cga/sctp/mis/core/templating/SelectOptionConfigs.java b/sctp-mis/src/main/java/org/cga/sctp/mis/core/templating/SelectOptionConfigs.java index c7bab471..7a2588f5 100644 --- a/sctp-mis/src/main/java/org/cga/sctp/mis/core/templating/SelectOptionConfigs.java +++ b/sctp-mis/src/main/java/org/cga/sctp/mis/core/templating/SelectOptionConfigs.java @@ -33,8 +33,10 @@ package org.cga.sctp.mis.core.templating; import org.cga.sctp.beneficiaries.Individual; +import org.cga.sctp.funders.Funder; import org.cga.sctp.location.Location; import org.cga.sctp.location.LocationCode; +import org.cga.sctp.location.LocationType; import org.cga.sctp.program.Program; import org.cga.sctp.program.ProgramUser; import org.cga.sctp.program.ProgramUserCandidate; @@ -49,6 +51,8 @@ import org.cga.sctp.transfers.agencies.TransferMethod; import org.cga.sctp.transfers.parameters.HouseholdParameterCondition; import org.cga.sctp.transfers.parameters.TransferParameter; +import org.cga.sctp.transfers.topups.TopUpHouseholdStatus; +import org.cga.sctp.transfers.topups.TopUpType; import org.cga.sctp.user.AccessLevel; import org.cga.sctp.user.Permission; import org.cga.sctp.user.SystemRole; @@ -159,8 +163,28 @@ public SelectOptionEntry educationZones() { return new SelectOptionEntry(EducationZone.class, "getId()", "getName()"); } + @Bean + public SelectOptionEntry funderOption() { + return new SelectOptionEntry(Funder.class, "getId()", "getName()"); + } + @Bean public SelectOptionEntry transferParameterOption() { return new SelectOptionEntry(TransferParameter.class, "getId()", "getTitle()"); } + + @Bean + public SelectOptionEntry topupTypes() { + return new SelectOptionEntry(TopUpType.class, "name()", "getDescription()"); + } + + @Bean + public SelectOptionEntry topupHouseholdStatuses() { + return new SelectOptionEntry(TopUpHouseholdStatus.class, "name()", "getDescription()"); + } + + @Bean + public SelectOptionEntry locationTypeOption() { + return new SelectOptionEntry(LocationType.class, "name()", "description"); + } } diff --git a/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/agencies/TransferAgenciesController.java b/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/agencies/TransferAgenciesController.java index fe423f8e..716c17b3 100644 --- a/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/agencies/TransferAgenciesController.java +++ b/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/agencies/TransferAgenciesController.java @@ -130,7 +130,9 @@ public ModelAndView processCreatePage(@AuthenticationPrincipal String username, transferAgency.setAddress(form.getAddress()); transferAgency.setRepresentativeName(form.getRepresentativeName()); transferAgency.setRepresentativeEmail(form.getRepresentativeEmail()); + transferAgency.setRepresentativePhone(form.getRepresentativePhone()); transferAgency.setWebsite(form.getWebsite()); + transferAgency.setPhone(form.getPhone()); transferAgency.setBranch(form.getBranch()); transferAgency.setCreatedAt(LocalDate.now()); transferAgency.setModifiedAt(transferAgency.getCreatedAt()); diff --git a/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/agencies/TransferAgencyForm.java b/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/agencies/TransferAgencyForm.java index 8119a8ee..a95a37c7 100644 --- a/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/agencies/TransferAgencyForm.java +++ b/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/agencies/TransferAgencyForm.java @@ -53,7 +53,7 @@ public class TransferAgencyForm { @NotNull(message = "Status is required") private Booleans active; - @NotEmpty + @NotEmpty(message = "Phone is required") @Length(min=10, max=15) private String phone; @@ -73,7 +73,6 @@ public class TransferAgencyForm { @Length(min=10, max=15) private String representativePhone; - @NotEmpty private String branch; private Long locationId; diff --git a/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/topups/TransferTopUpsController.java b/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/topups/TransferTopUpsController.java index 340efab3..4ee8096f 100644 --- a/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/topups/TransferTopUpsController.java +++ b/sctp-mis/src/main/java/org/cga/sctp/mis/transfers/topups/TransferTopUpsController.java @@ -32,12 +32,126 @@ package org.cga.sctp.mis.transfers.topups; +import org.cga.sctp.funders.Funder; +import org.cga.sctp.funders.FundersService; +import org.cga.sctp.location.Location; +import org.cga.sctp.location.LocationService; +import org.cga.sctp.location.LocationType; import org.cga.sctp.mis.core.SecuredBaseController; +import org.cga.sctp.mis.core.templating.Booleans; +import org.cga.sctp.program.Program; +import org.cga.sctp.program.ProgramService; +import org.cga.sctp.transfers.topups.*; +import org.cga.sctp.user.AdminAndStandardAccessOnly; +import org.cga.sctp.user.AuthenticatedUser; +import org.cga.sctp.user.AuthenticatedUserDetails; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; +import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import java.util.List; +import java.util.Optional; @Controller @RequestMapping("/transfers/topups") public class TransferTopUpsController extends SecuredBaseController { - // TODO: Implement me! + + @Autowired + private ProgramService programService; + + @Autowired + private FundersService fundersService; + + @Autowired + private LocationService locationService; + + @Autowired + private TopUpService topUpService; + + @GetMapping("/new") + @AdminAndStandardAccessOnly + public ModelAndView getNewPage(RedirectAttributes attributes) { + List programs = programService.getActivePrograms(); + List districts = locationService.getActiveDistricts(); + List funders = fundersService.getActiveFunders(); + + if (programs.isEmpty()) { + return redirectOnFailedCondition("/transfers/topups", "Cannot create TopUps when there are not Programmes registered", attributes); + } + + if (districts.isEmpty()) { + return redirectOnFailedCondition("/transfers/topups", "Cannot create TopUps when there are no Locations registered", attributes); + } + + if (funders.isEmpty()) { + return redirectOnFailedCondition("/transfers/topups", "Cannot create TopUps when there are no Funders registered", attributes); + } + + return view("transfers/topups/new") + .addObject("booleans", Booleans.values()) + .addObject("topupTypes", TopUpType.values()) + .addObject("householdStatuses", TopUpHouseholdStatus.values()) + .addObject("locationTypes", LocationType.values()) + .addObject("districts", districts) + .addObject("programs", programs) + .addObject("funders", funders); + } + + @PostMapping("/new") + @AdminAndStandardAccessOnly + public ModelAndView postNewPage(@AuthenticatedUserDetails AuthenticatedUser user, + @ModelAttribute("form") @Validated NewTopUpForm form, + BindingResult result, + RedirectAttributes redirectAttributes) { + List programs = programService.getActivePrograms(); + List districts = locationService.getActiveDistricts(); + List funders = fundersService.getActiveFunders(); + + if (result.hasErrors()) { + LoggerFactory.getLogger(getClass()).warn("Validation errors on creating topup {}", result.getAllErrors()); + return withDangerMessage("transfers/topups/new", "Please correct errors on the form") + .addObject(form) + .addObject("booleans", Booleans.values()) + .addObject("topupTypes", TopUpType.values()) + .addObject("householdStatuses", TopUpHouseholdStatus.values()) + .addObject("locationTypes", LocationType.values()) + .addObject("districts", districts) + .addObject("programs", programs) + .addObject("funders", funders); + } + + form.setUserId(user.id()); + + Optional topUp = topUpService.newTopup(form); + if (topUp.isPresent()) { + return view(redirectWithSuccessMessage("/transfers/topups", "TopUp created successfully", redirectAttributes)); + } + + return withDangerMessage( "transfers/topups/new", "Failed to create topups due to unknown error") + .addObject(form) + .addObject("booleans", Booleans.values()) + .addObject("topupTypes", TopUpType.values()) + .addObject("householdStatuses", TopUpHouseholdStatus.values()) + .addObject("locationTypes", LocationType.values()) + .addObject("districts", districts) + .addObject("programs", programs) + .addObject("funders", funders); + } + + @GetMapping + @AdminAndStandardAccessOnly + public ModelAndView getIndex() { + List topups = topUpService.findAllActive(); + return view("transfers/topups/list") + .addObject("topups", topups); + } + } diff --git a/sctp-mis/src/main/resources/templates/navbar.peb b/sctp-mis/src/main/resources/templates/navbar.peb index 3310ff7d..5e4c22c3 100644 --- a/sctp-mis/src/main/resources/templates/navbar.peb +++ b/sctp-mis/src/main/resources/templates/navbar.peb @@ -91,15 +91,15 @@ {{ message("navbar.transfers.transfer-parameters") }} + + Top Ups + {{ message("navbar.transfers.calculate-transfers") }} {{ message("navbar.transfers.list-beneficiaries") }} - - Top Ups - {# #} {# Generate Transfers#} {# #} diff --git a/sctp-mis/src/main/resources/templates/transfers/parameters/education/new.peb b/sctp-mis/src/main/resources/templates/transfers/parameters/education/new.peb index 84a5196e..3d96b130 100644 --- a/sctp-mis/src/main/resources/templates/transfers/parameters/education/new.peb +++ b/sctp-mis/src/main/resources/templates/transfers/parameters/education/new.peb @@ -16,6 +16,7 @@

Education Transfer Parameters

+ {{ showMessages(successMessage, infoMessage, dangerMessage, warningMessage) }}
diff --git a/sctp-mis/src/main/resources/templates/transfers/parameters/households/new.peb b/sctp-mis/src/main/resources/templates/transfers/parameters/households/new.peb index 2280f341..7c619af5 100644 --- a/sctp-mis/src/main/resources/templates/transfers/parameters/households/new.peb +++ b/sctp-mis/src/main/resources/templates/transfers/parameters/households/new.peb @@ -16,6 +16,7 @@

Household Transfer Parameters

+ {{ showMessages(successMessage, infoMessage, dangerMessage, warningMessage) }} diff --git a/sctp-mis/src/main/resources/templates/transfers/parameters/new.peb b/sctp-mis/src/main/resources/templates/transfers/parameters/new.peb index dc1970d3..d13e8e6c 100644 --- a/sctp-mis/src/main/resources/templates/transfers/parameters/new.peb +++ b/sctp-mis/src/main/resources/templates/transfers/parameters/new.peb @@ -16,6 +16,7 @@

New Transfer Parameter

+ {{ showMessages(successMessage, infoMessage, dangerMessage, warningMessage) }} diff --git a/sctp-mis/src/main/resources/templates/transfers/topups/edit.peb b/sctp-mis/src/main/resources/templates/transfers/topups/edit.peb new file mode 100644 index 00000000..e69de29b diff --git a/sctp-mis/src/main/resources/templates/transfers/topups/list.peb b/sctp-mis/src/main/resources/templates/transfers/topups/list.peb new file mode 100644 index 00000000..c5165bb5 --- /dev/null +++ b/sctp-mis/src/main/resources/templates/transfers/topups/list.peb @@ -0,0 +1,66 @@ + {% extends "../base" %} + {% block contextMenu %} +
+ {{ showMessages(successMessage, infoMessage, dangerMessage, warningMessage) }} + + +
+ {% endblock %} + {% block content %} + +
+
+
Top Ups
+
+
+ + + + + + + + + + + + + + {% for t in topups %} + + + + + + + + + + {% endfor %} + +
NameTypeDistrictIs CategoricalAmount ProjectedExecuted?Actions
{{ t.name }}{{ t.topupType }}{{ t.locationId }}{{ yesOrNo(t.isCategorical) }}{{ t.amountProjected }}{{ yesOrNo(t.isExecuted) }} + + +
+
+
+ {% endblock %} \ No newline at end of file diff --git a/sctp-mis/src/main/resources/templates/transfers/topups/new.peb b/sctp-mis/src/main/resources/templates/transfers/topups/new.peb new file mode 100644 index 00000000..24385533 --- /dev/null +++ b/sctp-mis/src/main/resources/templates/transfers/topups/new.peb @@ -0,0 +1,61 @@ +{% extends "../base" %} +{% import "../../utils/form" %} +{% block contextMenu %} +
+ +
+{% endblock %} + +{% block content %} + +
+
+
New Top Up
+
+
+ {{ showMessages(successMessage, infoMessage, dangerMessage, warningMessage) }} + + {{ csrf(_csrf) }} + + {{ validatedSelectField('Program', 'programId', programs, form.programId, true, '') }} + {{ validatedTextField('Name', 'name', form.name, 1, 100, true, '') }} + {{ validatedSelectField('TopUp Types', 'topupType', topupTypes, form.topupType, true, '') }} +
+ {{ validatedTextField('Percentage', 'percentage', form.percentage, 0, 3, false, '') }} +
+ {{ validatedSelectField('Funder / Sponsor', 'funderId', funders, form.funderId, true, '') }} + {{ validatedSelectField('Household Status', 'householdStatus', householdStatuses, form.householdStatus, true, '') }} + {{ validatedSelectField('Active', 'active', booleans, form.active, true, '') }} + +
+ {{ validatedSelectField('Location Type', 'locationType', locationTypes, form.locationType, true, '') }} + {{ validatedSelectField('Location', 'locationId', districts, form.locationId, true, '') }} +{# TODO: partial areas#} +{# #} +
+{# TODO: Funds discounted and categorical targeting criteria#} +{#
#} +{# #} +{#
#} + +{#
#} +{# #} +{# {{ validatedSelectField('Categorical Criteria', 'categoricalTargetingCriteriaId', categoricalCriterias, form.categoricalTargetingCriteriaId, false, '') }}#} +{#
#} + + +
+

+ Please note that created Topups will affect subsequent transfer calculations. +

+ +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/sctp-mis/src/main/resources/templates/transfers/topups/view.peb b/sctp-mis/src/main/resources/templates/transfers/topups/view.peb new file mode 100644 index 00000000..e69de29b diff --git a/sctp-mis/src/main/resources/templates/utils/form.peb b/sctp-mis/src/main/resources/templates/utils/form.peb index c8790174..c3d2bfc6 100644 --- a/sctp-mis/src/main/resources/templates/utils/form.peb +++ b/sctp-mis/src/main/resources/templates/utils/form.peb @@ -87,3 +87,36 @@ {% endif %} {% endmacro %} + + {% macro validatedTextField(labelName, id, value, min, max, required, classes) %} +
+
+ +
+
+
+
+ {{ textField(id, value, min, max, required, classes) }} +
+ {{ printFieldErrors(getFieldErrors('form', id)) }} +
+
+
+ {% endmacro %} + + +{% macro validatedSelectField(labelName, id, options, value, required, classes) %} +
+
+ +
+
+
+
+ {{ selectField(id, options, value, require) }} +
+ {{ printFieldErrors(getFieldErrors('form', id)) }} +
+
+
+{% endmacro %}