From d429bb79b482c5df60b8d91eaeb56a18b87b74fe Mon Sep 17 00:00:00 2001 From: Klaus Richarz Date: Wed, 27 Nov 2024 19:48:51 +0100 Subject: [PATCH] Introduce StatisticValue entity and repository Added StatisticValue entity to track statistics, including required DDL. Updated StatisticService to handle statistics for created, updated, and deleted time reports. Implemented methods to update or save statistics and ensure persistence in database. --- .../org/tb/favorites/domain/Favorite.java | 44 ++++++- .../tb/statistic/domain/StatisticValue.java | 111 ++++++++++++++++++ .../persistence/StatisticValueRepository.java | 13 ++ .../statistic/service/StatisticService.java | 93 ++++++++++++--- .../db/changelog/db.changelog-master.yaml | 39 ++++++ 5 files changed, 277 insertions(+), 23 deletions(-) create mode 100644 src/main/java/org/tb/statistic/domain/StatisticValue.java create mode 100644 src/main/java/org/tb/statistic/persistence/StatisticValueRepository.java diff --git a/src/main/java/org/tb/favorites/domain/Favorite.java b/src/main/java/org/tb/favorites/domain/Favorite.java index 291a1c7a7..183117143 100644 --- a/src/main/java/org/tb/favorites/domain/Favorite.java +++ b/src/main/java/org/tb/favorites/domain/Favorite.java @@ -1,32 +1,68 @@ package org.tb.favorites.domain; +import static lombok.AccessLevel.PRIVATE; + import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Lob; +import java.util.Objects; import lombok.AllArgsConstructor; import lombok.Builder; -import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.Persistable; @Builder -@Data @Entity -@NoArgsConstructor +@Getter +@Setter @AllArgsConstructor -public class Favorite { +@NoArgsConstructor +public class Favorite implements Persistable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Setter(PRIVATE) private Long id; + private Long employeeId; + private Long employeeorderId; + private Integer hours; + private Integer minutes; + @Lob @Column(columnDefinition = "text") private String comment; + @Override + public boolean isNew() { + return id == null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if(id == null) return false; + Favorite that = (Favorite) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + if(id == null) return 0; + return Objects.hash(id); + } + } diff --git a/src/main/java/org/tb/statistic/domain/StatisticValue.java b/src/main/java/org/tb/statistic/domain/StatisticValue.java new file mode 100644 index 000000000..1f296e061 --- /dev/null +++ b/src/main/java/org/tb/statistic/domain/StatisticValue.java @@ -0,0 +1,111 @@ +package org.tb.statistic.domain; + +import static jakarta.persistence.AccessType.FIELD; +import static lombok.AccessLevel.PRIVATE; + +import jakarta.persistence.Access; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import java.time.Duration; +import java.util.Objects; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.Persistable; + +@Entity +@Access(FIELD) +@Table(name = "statistic_value", indexes = { + @Index(name = "idx_main", columnList = "category, key, object_id") +}) +@NoArgsConstructor(access = PRIVATE) +public class StatisticValue implements Persistable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Getter + private Long id; + + @Getter + private String category; + + @Column(name = "`key`") + @Getter + private String key; + + @Getter + @Column(name = "object_id") + private long objectId; + + @Setter + private long value; + + @Column(length = 4000) + @Getter + @Setter + private String comment; + + public StatisticValue(String category, String key, long objectId, Duration value, String comment) { + this.category = category; + this.key = key; + this.objectId = objectId; + this.comment = comment; + this.value = value.toMinutes(); + } + + public Duration getAsDuration() { + return Duration.ofMinutes(value); + } + + public void setValue(Duration value) { + this.value = value.toMinutes(); + } + + @Override + public boolean isNew() { + return id == null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (id == null) { + return false; + } + StatisticValue that = (StatisticValue) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + if (id == null) { + return 0; + } + return Objects.hash(id); + } + +} + +// Corresponding DDL: + +/* +CREATE TABLE statistic_value ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + category VARCHAR(255), + key VARCHAR(255), + object_id BIGINT, + value BIGINT, + comment VARCHAR(4000), + INDEX idx_main (category, key, object_id) +); +*/ diff --git a/src/main/java/org/tb/statistic/persistence/StatisticValueRepository.java b/src/main/java/org/tb/statistic/persistence/StatisticValueRepository.java new file mode 100644 index 000000000..212c0a1e7 --- /dev/null +++ b/src/main/java/org/tb/statistic/persistence/StatisticValueRepository.java @@ -0,0 +1,13 @@ +package org.tb.statistic.persistence; + +import java.util.Optional; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; +import org.tb.statistic.domain.StatisticValue; + +@Repository +public interface StatisticValueRepository extends CrudRepository { + + Optional findByCategoryAndKeyAndObjectId(String category, String key, long objectId); + +} diff --git a/src/main/java/org/tb/statistic/service/StatisticService.java b/src/main/java/org/tb/statistic/service/StatisticService.java index bfd757c96..4b54bc669 100644 --- a/src/main/java/org/tb/statistic/service/StatisticService.java +++ b/src/main/java/org/tb/statistic/service/StatisticService.java @@ -1,7 +1,12 @@ package org.tb.statistic.service; import static java.time.Duration.ofMinutes; +import static java.time.LocalDateTime.now; +import java.time.Duration; +import java.time.LocalDate; +import java.time.YearMonth; +import java.util.LinkedList; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -9,7 +14,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.tb.common.domain.AuditedEntity; -import org.tb.common.util.DurationUtils; import org.tb.dailyreport.domain.TimereportDTO; import org.tb.dailyreport.event.TimereportsCreatedOrUpdatedEvent; import org.tb.dailyreport.event.TimereportsDeletedEvent; @@ -20,6 +24,8 @@ import org.tb.order.service.CustomerorderService; import org.tb.order.service.EmployeeorderService; import org.tb.order.service.SuborderService; +import org.tb.statistic.domain.StatisticValue; +import org.tb.statistic.persistence.StatisticValueRepository; @Slf4j @Service @@ -31,36 +37,61 @@ public class StatisticService { private final EmployeeorderService employeeorderService; private final SuborderService suborderService; private final CustomerorderService customerorderService; + private final StatisticValueRepository statisticValueRepository; @EventListener void onTimereportsCreatedOrUpdated(TimereportsCreatedOrUpdatedEvent event) { - var employeeorders = event.getIds().stream() + var timereports = event.getIds().stream() .map(timereportService::getTimereportById) + .toList(); + var minDate = timereports.stream() + .map(TimereportDTO::getReferenceday) + .min(LocalDate::compareTo) + .orElse(LocalDate.now()); + var maxDate = timereports.stream() + .map(TimereportDTO::getReferenceday) + .max(LocalDate::compareTo) + .orElse(LocalDate.now()); + + var employeeorders = timereports.stream() .map(TimereportDTO::getEmployeeorderId) .distinct() .map(employeeorderService::getEmployeeorderById) .toList(); - generateTimereportStatistics(employeeorders); + + var comment = "time report(s) created or updated @ %s".formatted(now()); + + generateOrderStatistics(employeeorders, minDate, maxDate, comment); } @EventListener void onTimereportsDeleted(TimereportsDeletedEvent event) { + var referencedDays = event.getIds().stream() + .map(TimereportDeleteId::getReferenceDay) + .distinct() + .toList(); + var minDate = referencedDays.stream() + .min(LocalDate::compareTo) + .orElse(LocalDate.now()); + var maxDate = referencedDays.stream() + .max(LocalDate::compareTo) + .orElse(LocalDate.now()); + var employeeorders = event.getIds().stream() .map(TimereportDeleteId::getEmployeeorderId) .distinct() .map(employeeorderService::getEmployeeorderById) .toList(); - generateTimereportStatistics(employeeorders); + + var comment = "time report(s) deleted @ %s".formatted(now()); + + generateOrderStatistics(employeeorders, minDate, maxDate, comment); } - private void generateTimereportStatistics(List employeeorders) { + private void generateOrderStatistics(List employeeorders, LocalDate min, LocalDate max, String comment) { employeeorders.forEach(eo -> { var duration = timereportService.getTotalDurationMinutesForEmployeeOrder(eo.getId()); - log.info("EO: Duration for {} of {}: {}", - eo.getSuborder().getCompleteOrderSign(), - eo.getEmployeecontract().getEmployee().getSign(), - DurationUtils.format(ofMinutes(duration)) - ); + updateStatistic("EMPLOYEEORDER", "timereport.duration.total", eo.getId(), ofMinutes(duration), comment); }); var suborders = employeeorders.stream() .map(Employeeorder::getSuborder) @@ -70,11 +101,15 @@ private void generateTimereportStatistics(List employeeorders) { .map(suborderService::getSuborderById) .toList(); suborders.forEach(so -> { - var duration = timereportService.getTotalDurationMinutesForSuborders(List.of(so.getId())); - log.info("SO Duration for {}: {}", - so.getCompleteOrderSign(), - DurationUtils.format(ofMinutes(duration)) - ); + { + var duration = timereportService.getTotalDurationMinutesForSuborders(List.of(so.getId())); + updateStatistic("SUBORDER", "timereport.duration.total", so.getId(), ofMinutes(duration), comment); + } + var months = getMonths(min, max); + months.forEach(month -> { + var duration = timereportService.getTotalDurationMinutesForSuborder(so.getId(), month.atDay(1), month.atEndOfMonth()); + updateStatistic("SUBORDER", "timereport.duration." + month, so.getId(), ofMinutes(duration), comment); + }); }); var customerorders = suborders.stream() .map(Suborder::getCustomerorder) @@ -84,11 +119,31 @@ private void generateTimereportStatistics(List employeeorders) { .toList(); customerorders.forEach(co -> { var duration = timereportService.getTotalDurationMinutesForCustomerOrder(co.getId()); - log.info("CO Duration for {}: {}", - co.getSign(), - DurationUtils.format(ofMinutes(duration)) - ); + updateStatistic("CUSTOMERORDER", "timereport.duration.total", co.getId(), ofMinutes(duration), comment); }); } + private List getMonths(LocalDate min, LocalDate max) { + List result = new LinkedList<>(); + + YearMonth current = YearMonth.from(min); + YearMonth end = YearMonth.from(max); + + while (!current.isAfter(end)) { + result.add(current); + current = current.plusMonths(1); + } + + return result; + } + + private void updateStatistic(String category, String key, long objectId, Duration value, String comment) { + var statisticValue = statisticValueRepository + .findByCategoryAndKeyAndObjectId(category, key, objectId) + .orElseGet(() -> new StatisticValue(category, key, objectId, value, comment)); + statisticValue.setValue(value); + statisticValue.setComment(comment); + statisticValueRepository.save(statisticValue); + } + } diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index b4c5650b5..0b1a06895 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -972,5 +972,44 @@ databaseChangeLog: value: OVERTIME_COMPENSATED where: type = 'PARTIALLY' + - changeSet: + id: 29 + author: kr + comment: Create table statistic_value + changes: + - createTable: + tableName: statistic_value + columns: + - column: + name: id + type: bigint + autoIncrement: true + constraints: + primaryKey: true + - column: + name: category + type: varchar(255) + - column: + name: key + type: varchar(255) + - column: + name: object_id + type: bigint + - column: + name: value + type: bigint + - column: + name: comment + type: varchar(4000) + - createIndex: + indexName: idx_main + tableName: statistic_value + columns: + - column: + name: category + - column: + name: key + - column: + name: object_id # - include: # file: db/changelog/includes/addAllEmployees.sql \ No newline at end of file