From bb80e35e04431ed0753cf37bf78cc90995d67159 Mon Sep 17 00:00:00 2001 From: Szymon Radziszewski Date: Tue, 5 Dec 2023 13:15:14 +0100 Subject: [PATCH] AO-802: Added PriceChange entity --- .../referencedata/domain/PriceChange.java | 100 ++++++++++++++++++ .../domain/ProgramOrderable.java | 43 +++++--- .../referencedata/dto/PriceChangeDto.java | 87 +++++++++++++++ .../dto/ProgramOrderableDto.java | 13 +++ .../ProgramOrderableImportPersister.java | 3 +- ...1111221730515__add_price_changes_table.sql | 9 ++ .../referencedata/domain/PriceChangeTest.java | 72 +++++++++++++ .../referencedata/dto/PriceChangeDtoTest.java | 40 +++++++ .../testbuilder/PriceChangeDataBuilder.java | 82 ++++++++++++++ .../ProgramOrderableDataBuilder.java | 10 +- 10 files changed, 443 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/openlmis/referencedata/domain/PriceChange.java create mode 100644 src/main/java/org/openlmis/referencedata/dto/PriceChangeDto.java create mode 100644 src/main/resources/db/migration/20231111221730515__add_price_changes_table.sql create mode 100644 src/test/java/org/openlmis/referencedata/domain/PriceChangeTest.java create mode 100644 src/test/java/org/openlmis/referencedata/dto/PriceChangeDtoTest.java create mode 100644 src/test/java/org/openlmis/referencedata/testbuilder/PriceChangeDataBuilder.java diff --git a/src/main/java/org/openlmis/referencedata/domain/PriceChange.java b/src/main/java/org/openlmis/referencedata/domain/PriceChange.java new file mode 100644 index 000000000..901cbf029 --- /dev/null +++ b/src/main/java/org/openlmis/referencedata/domain/PriceChange.java @@ -0,0 +1,100 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org. + */ + +package org.openlmis.referencedata.domain; + +import java.time.ZonedDateTime; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.Type; +import org.joda.money.Money; + +@Getter +@Setter +@Entity +@Table(name = "price_changes") +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class PriceChange extends BaseEntity { + + @ManyToOne(cascade = {CascadeType.ALL}) + @JoinColumn(name = "programOrderableId") + private ProgramOrderable programOrderable; + + @Type(type = "org.openlmis.referencedata.util.CustomSingleColumnMoneyUserType") + private Money price; + + @ManyToOne + @JoinColumn(name = "authorId", nullable = false) + private User author; + + @Column(columnDefinition = "timestamp with time zone", nullable = false) + private ZonedDateTime occurredDate; + + /** + * Creates new instance based on data from {@link PriceChange.Importer}. + * + * @param importer instance of {@link PriceChange.Importer} + * @return new instance of PriceChange. + */ + public static PriceChange newInstance(PriceChange.Importer importer, User author) { + PriceChange priceChange = new PriceChange(); + priceChange.setPrice(importer.getPrice()); + priceChange.setOccurredDate(importer.getOccurredDate()); + priceChange.setAuthor(author); + + return priceChange; + } + + /** + * Export this object to the specified exporter (DTO). + * + * @param exporter exporter to export to + */ + public void export(PriceChange.Exporter exporter) { + exporter.setPrice(price); + exporter.setAuthor(author); + exporter.setOccurredDate(occurredDate); + } + + public interface Exporter { + + void setPrice(Money price); + + void setAuthor(User author); + + void setOccurredDate(ZonedDateTime occurredDate); + + } + + public interface Importer { + + Money getPrice(); + + ZonedDateTime getOccurredDate(); + + } + +} diff --git a/src/main/java/org/openlmis/referencedata/domain/ProgramOrderable.java b/src/main/java/org/openlmis/referencedata/domain/ProgramOrderable.java index c7fb97a52..d1a2a9174 100644 --- a/src/main/java/org/openlmis/referencedata/domain/ProgramOrderable.java +++ b/src/main/java/org/openlmis/referencedata/domain/ProgramOrderable.java @@ -22,13 +22,17 @@ import static org.openlmis.referencedata.web.csv.processor.CsvCellProcessors.PROGRAM_TYPE; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.UUID; +import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.UniqueConstraint; import lombok.AllArgsConstructor; @@ -39,6 +43,7 @@ import org.javers.core.metamodel.annotation.TypeName; import org.joda.money.CurrencyUnit; import org.joda.money.Money; +import org.openlmis.referencedata.dto.PriceChangeDto; import org.openlmis.referencedata.web.csv.model.ImportField; @Entity @@ -107,9 +112,17 @@ public class ProgramOrderable extends BaseEntity { @ImportField(name = PRICE_PER_PACK, type = MONEY_TYPE) private Money pricePerPack; + @OneToMany( + mappedBy = "programOrderable", + cascade = CascadeType.ALL, + orphanRemoval = true) + @Getter + @Setter + private List priceChanges = new ArrayList<>(); + private ProgramOrderable(Program program, - Orderable product, - OrderableDisplayCategory orderableDisplayCategory) { + Orderable product, + OrderableDisplayCategory orderableDisplayCategory) { this.program = program; this.product = product; this.orderableDisplayCategory = orderableDisplayCategory; @@ -159,9 +172,9 @@ public boolean isForProgram(Program program) { * @return see other */ public static final ProgramOrderable createNew(Program program, - OrderableDisplayCategory category, - Orderable product, - CurrencyUnit currencyUnit) { + OrderableDisplayCategory category, + Orderable product, + CurrencyUnit currencyUnit) { ProgramOrderable programOrderable = new ProgramOrderable(program, product, category); programOrderable.pricePerPack = Money.of(currencyUnit, BigDecimal.ZERO); return programOrderable; @@ -179,14 +192,14 @@ public static final ProgramOrderable createNew(Program program, * @return a new ProgramOrderable. */ public static final ProgramOrderable createNew(Program program, - OrderableDisplayCategory category, - Orderable product, - Integer dosesPerPatient, - boolean active, - boolean fullSupply, - int displayOrder, - Money pricePerPack, - CurrencyUnit currencyUnit) { + OrderableDisplayCategory category, + Orderable product, + Integer dosesPerPatient, + boolean active, + boolean fullSupply, + int displayOrder, + Money pricePerPack, + CurrencyUnit currencyUnit) { ProgramOrderable programOrderable = createNew(program, category, product, currencyUnit); programOrderable.dosesPerPatient = dosesPerPatient; programOrderable.active = active; @@ -262,7 +275,7 @@ public void export(Exporter exporter) { if (pricePerPack != null) { exporter.setPricePerPack(pricePerPack); } - + exporter.setPriceChanges(PriceChangeDto.newInstance(priceChanges)); } public interface Exporter { @@ -283,6 +296,8 @@ public interface Exporter { void setDosesPerPatient(Integer dosesPerPatient); void setPricePerPack(Money pricePerPack); + + void setPriceChanges(List priceChanges); } public interface Importer { diff --git a/src/main/java/org/openlmis/referencedata/dto/PriceChangeDto.java b/src/main/java/org/openlmis/referencedata/dto/PriceChangeDto.java new file mode 100644 index 000000000..48b6f5d59 --- /dev/null +++ b/src/main/java/org/openlmis/referencedata/dto/PriceChangeDto.java @@ -0,0 +1,87 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org. + */ + +package org.openlmis.referencedata.dto; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.joda.money.Money; +import org.openlmis.referencedata.domain.PriceChange; +import org.openlmis.referencedata.domain.User; +import org.openlmis.referencedata.serializer.MoneyDeserializer; +import org.openlmis.referencedata.serializer.MoneySerializer; +import org.openlmis.referencedata.web.BaseController; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ToString +public final class PriceChangeDto extends BaseDto implements PriceChange.Exporter, + PriceChange.Importer { + + @JsonSerialize(using = MoneySerializer.class) + @JsonDeserialize(using = MoneyDeserializer.class) + private Money price; + private ZonedDateTime occurredDate; + private UserObjectReferenceDto author; + + /** + * Create new List containing PriceChangeDto based on given a set of {@link PriceChange}. + * + * @param priceChanges Price changes. + * @return a list containing dtos for all price changes. + */ + public static List newInstance(List priceChanges) { + if (priceChanges == null) { + return Collections.emptyList(); + } + return priceChanges.stream() + .map(PriceChangeDto::newInstance) + .collect(Collectors.toList()); + } + + /** + * Create new PriceChangeDto based on a given {@link PriceChange}. + * + * @param priceChange Price change. + * @return a price change object converted to dto. + */ + public static PriceChangeDto newInstance(PriceChange priceChange) { + PriceChangeDto dto = new PriceChangeDto(); + priceChange.export(dto); + return dto; + } + + @Override + public void setAuthor(User author) { + this.author = new UserObjectReferenceDto("referencedata", + BaseController.API_PATH + "/users", author.getId()); + this.author.setFirstName(author.getFirstName()); + this.author.setLastName(author.getLastName()); + } + +} diff --git a/src/main/java/org/openlmis/referencedata/dto/ProgramOrderableDto.java b/src/main/java/org/openlmis/referencedata/dto/ProgramOrderableDto.java index f43ffda85..1150b4b87 100644 --- a/src/main/java/org/openlmis/referencedata/dto/ProgramOrderableDto.java +++ b/src/main/java/org/openlmis/referencedata/dto/ProgramOrderableDto.java @@ -18,7 +18,12 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import com.google.common.collect.Lists; +import java.util.Collections; import java.util.HashSet; +import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.UUID; import lombok.AllArgsConstructor; @@ -60,6 +65,8 @@ public class ProgramOrderableDto extends BaseDto @JsonDeserialize(using = MoneyDeserializer.class) private Money pricePerPack; + private List priceChanges; + /** * Create new list of ProgramOrderableDto based on given list of {@link ProgramOrderable}. * @@ -89,4 +96,10 @@ public static ProgramOrderableDto newInstance(ProgramOrderable po) { return programDto; } + + public List getPriceChanges() { + return Lists.newArrayList(Optional.ofNullable(priceChanges) + .orElse(Collections.emptyList())); + } + } diff --git a/src/main/java/org/openlmis/referencedata/service/export/ProgramOrderableImportPersister.java b/src/main/java/org/openlmis/referencedata/service/export/ProgramOrderableImportPersister.java index 8bd50af6e..d70db0e5b 100644 --- a/src/main/java/org/openlmis/referencedata/service/export/ProgramOrderableImportPersister.java +++ b/src/main/java/org/openlmis/referencedata/service/export/ProgramOrderableImportPersister.java @@ -90,7 +90,8 @@ public List createOrUpdate(List dtoL dto.getDisplayOrder(), dto.getDosesPerPatient(), dto.getPricePerPack() != null ? Money.of(currency, - Double.parseDouble(dto.getPricePerPack())) : null + Double.parseDouble(dto.getPricePerPack())) : null, + null ); ProgramOrderable programOrderable = programOrderableRepository diff --git a/src/main/resources/db/migration/20231111221730515__add_price_changes_table.sql b/src/main/resources/db/migration/20231111221730515__add_price_changes_table.sql new file mode 100644 index 000000000..37cf34490 --- /dev/null +++ b/src/main/resources/db/migration/20231111221730515__add_price_changes_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE price_changes ( + id UUID PRIMARY KEY, + programOrderableId UUID, + price numeric(19,2) NOT NULL, + authorId uuid NOT NULL, + occurredDate TIMESTAMP WITH TIME ZONE NOT NULL, + CONSTRAINT fk_program_orderable FOREIGN KEY (programOrderableId) REFERENCES program_orderables(id), + CONSTRAINT fk_author FOREIGN KEY (authorId) REFERENCES users(id) +); \ No newline at end of file diff --git a/src/test/java/org/openlmis/referencedata/domain/PriceChangeTest.java b/src/test/java/org/openlmis/referencedata/domain/PriceChangeTest.java new file mode 100644 index 000000000..701475546 --- /dev/null +++ b/src/test/java/org/openlmis/referencedata/domain/PriceChangeTest.java @@ -0,0 +1,72 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org. + */ + +package org.openlmis.referencedata.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; +import org.junit.Test; +import org.openlmis.referencedata.dto.PriceChangeDto; +import org.openlmis.referencedata.testbuilder.PriceChangeDataBuilder; +import org.openlmis.referencedata.testbuilder.ProgramOrderableDataBuilder; +import org.openlmis.referencedata.testbuilder.UserDataBuilder; + +public class PriceChangeTest { + + @Test + public void equalsContract() { + ProgramOrderable po1 = new ProgramOrderableDataBuilder().build(); + ProgramOrderable po2 = new ProgramOrderable(); + + User u1 = new UserDataBuilder().build(); + User u2 = new User(); + + EqualsVerifier + .forClass(PriceChange.class) + .withRedefinedSuperclass() + .withPrefabValues(ProgramOrderable.class, po1, po2) + .withPrefabValues(User.class, u1, u2) + .suppress(Warning.ALL_FIELDS_SHOULD_BE_USED) + .verify(); + } + + @Test + public void shouldCreateNewInstance() { + User author = new UserDataBuilder().build(); + PriceChange priceChange = new PriceChangeDataBuilder().withAuthor(author).build(); + PriceChangeDto importer = PriceChangeDto.newInstance(priceChange); + + PriceChange newInstance = PriceChange.newInstance(importer, author); + + assertThat(importer.getPrice()).isEqualTo(newInstance.getPrice()); + assertThat(importer.getAuthor().getId()).isEqualTo(newInstance.getAuthor().getId()); + assertThat(importer.getOccurredDate()).isEqualTo(newInstance.getOccurredDate()); + } + + @Test + public void shouldExportData() { + PriceChange priceChange = new PriceChangeDataBuilder().build(); + PriceChangeDto dto = new PriceChangeDto(); + + priceChange.export(dto); + + assertThat(dto.getPrice()).isEqualTo(priceChange.getPrice()); + assertThat(dto.getAuthor().getId()).isEqualTo(priceChange.getAuthor().getId()); + assertThat(dto.getOccurredDate()).isEqualTo(priceChange.getOccurredDate()); + } + +} diff --git a/src/test/java/org/openlmis/referencedata/dto/PriceChangeDtoTest.java b/src/test/java/org/openlmis/referencedata/dto/PriceChangeDtoTest.java new file mode 100644 index 000000000..e23f9998b --- /dev/null +++ b/src/test/java/org/openlmis/referencedata/dto/PriceChangeDtoTest.java @@ -0,0 +1,40 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org. + */ + +package org.openlmis.referencedata.dto; + +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; +import org.junit.Test; +import org.openlmis.referencedata.ToStringTestUtils; + +public class PriceChangeDtoTest { + + @Test + public void equalsContract() { + EqualsVerifier + .forClass(PriceChangeDto.class) + .withRedefinedSuperclass() + .suppress(Warning.NONFINAL_FIELDS) + .verify(); + } + + @Test + public void shouldImplementToString() { + PriceChangeDto priceChangeDto = new PriceChangeDto(); + ToStringTestUtils.verify(PriceChangeDto.class, priceChangeDto); + } + +} diff --git a/src/test/java/org/openlmis/referencedata/testbuilder/PriceChangeDataBuilder.java b/src/test/java/org/openlmis/referencedata/testbuilder/PriceChangeDataBuilder.java new file mode 100644 index 000000000..60b63f8cd --- /dev/null +++ b/src/test/java/org/openlmis/referencedata/testbuilder/PriceChangeDataBuilder.java @@ -0,0 +1,82 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org. + */ + +package org.openlmis.referencedata.testbuilder; + +import java.time.ZonedDateTime; +import java.util.UUID; +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; +import org.openlmis.referencedata.domain.PriceChange; +import org.openlmis.referencedata.domain.ProgramOrderable; +import org.openlmis.referencedata.domain.User; + +public class PriceChangeDataBuilder { + + private UUID id; + private ProgramOrderable programOrderable; + private Money price; + private User author; + private ZonedDateTime occurredDate; + + /** + * Returns instance of {@link PriceChangeDataBuilder} with sample data. + */ + public PriceChangeDataBuilder() { + id = UUID.randomUUID(); + programOrderable = new ProgramOrderableDataBuilder().build(); + price = Money.of(CurrencyUnit.of("USD"), 0); + author = new UserDataBuilder().build(); + occurredDate = ZonedDateTime.now(); + } + + /** + * Builds instance of {@link PriceChange}. + */ + public PriceChange build() { + PriceChange priceChange = buildAsNew(); + priceChange.setId(id); + + return priceChange; + } + + /** + * Builds instance of {@link PriceChange} without id field. + */ + public PriceChange buildAsNew() { + return new PriceChange(programOrderable, price, author, occurredDate); + } + + public PriceChangeDataBuilder withProgramOrderable(ProgramOrderable programOrderable) { + this.programOrderable = programOrderable; + return this; + } + + public PriceChangeDataBuilder withPrice(Money price) { + this.price = price; + return this; + } + + public PriceChangeDataBuilder withAuthor(User author) { + this.author = author; + return this; + } + + public PriceChangeDataBuilder withOccurredDate(ZonedDateTime occurredDate) { + this.occurredDate = occurredDate; + return this; + } + +} diff --git a/src/test/java/org/openlmis/referencedata/testbuilder/ProgramOrderableDataBuilder.java b/src/test/java/org/openlmis/referencedata/testbuilder/ProgramOrderableDataBuilder.java index 4bcb1f585..a6b8ac710 100644 --- a/src/test/java/org/openlmis/referencedata/testbuilder/ProgramOrderableDataBuilder.java +++ b/src/test/java/org/openlmis/referencedata/testbuilder/ProgramOrderableDataBuilder.java @@ -15,11 +15,14 @@ package org.openlmis.referencedata.testbuilder; +import java.util.Collections; +import java.util.List; import java.util.UUID; import org.joda.money.CurrencyUnit; import org.joda.money.Money; import org.openlmis.referencedata.domain.Orderable; import org.openlmis.referencedata.domain.OrderableDisplayCategory; +import org.openlmis.referencedata.domain.PriceChange; import org.openlmis.referencedata.domain.Program; import org.openlmis.referencedata.domain.ProgramOrderable; @@ -35,6 +38,7 @@ public class ProgramOrderableDataBuilder { private Orderable product; private CurrencyUnit currencyUnit; private Money pricePerPack; + private List priceChanges; /** * Returns instance of {@link ProgramOrderableDataBuilder} with sample data. @@ -50,6 +54,7 @@ public ProgramOrderableDataBuilder() { product = new OrderableDataBuilder().build(); currencyUnit = CurrencyUnit.of("USD"); pricePerPack = Money.of(currencyUnit, 0); + priceChanges = Collections.emptyList(); } /** @@ -66,8 +71,11 @@ public ProgramOrderable build() { * Builds instance of {@link ProgramOrderable} without id field. */ public ProgramOrderable buildAsNew() { - return ProgramOrderable.createNew(program, orderableDisplayCategory, product, dosesPerPatient, + ProgramOrderable programOrderable = ProgramOrderable.createNew( + program, orderableDisplayCategory, product, dosesPerPatient, active, fullSupply, displayOrder, pricePerPack, currencyUnit); + programOrderable.setPriceChanges(priceChanges); + return programOrderable; } public ProgramOrderableDataBuilder withProgram(Program program) {