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