diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/MeterReadingDto.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/MeterReadingDto.java index b508c22b..ed20abe9 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/MeterReadingDto.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/MeterReadingDto.java @@ -19,17 +19,19 @@ package org.greenbuttonalliance.espi.common.dto.usage; -import org.greenbuttonalliance.espi.common.dto.atom.LinkDto; - import jakarta.xml.bind.annotation.*; -import java.time.OffsetDateTime; -import java.util.List; /** * MeterReading DTO record for JAXB XML marshalling/unmarshalling. - * - * Represents a meter reading containing interval blocks and reading types. - * Supports Atom protocol XML wrapping. + * + * Represents a meter reading - a set of values obtained from the meter. + * Per ESPI 4.0 specification, MeterReading extends IdentifiedObject but + * contains NO child elements. Relationships to ReadingType and IntervalBlock + * are expressed via Atom links, not embedded XML elements. + * + * Complies with espi.xsd MeterReading complexType definition. + * + * @see NAESB ESPI 4.0 */ @XmlRootElement(name = "MeterReading", namespace = "http://naesb.org/espi") @XmlAccessorType(XmlAccessType.FIELD) diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/MeterReadingMapper.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/MeterReadingMapper.java index 78acb60a..46e39986 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/MeterReadingMapper.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/MeterReadingMapper.java @@ -22,24 +22,20 @@ import org.greenbuttonalliance.espi.common.domain.usage.MeterReadingEntity; import org.greenbuttonalliance.espi.common.dto.usage.MeterReadingDto; import org.greenbuttonalliance.espi.common.mapper.BaseIdentifiedObjectMapper; -import org.greenbuttonalliance.espi.common.mapper.BaseMapperUtils; import org.greenbuttonalliance.espi.common.mapper.DateTimeMapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; /** * MapStruct mapper for converting between MeterReadingEntity and MeterReadingDto. - * - * Handles the conversion between the JPA entity used for persistence and the DTO + * + * Per ESPI 4.0 specification, MeterReading has NO child elements - only relationships + * expressed via Atom links. The DTO contains NO fields beyond id/uuid. + * + * Handles the conversion between the JPA entity used for persistence and the DTO * used for JAXB XML marshalling in the Green Button API. */ -@Mapper(componentModel = "spring", uses = { - DateTimeMapper.class, - BaseMapperUtils.class, - IntervalBlockMapper.class, - ReadingTypeMapper.class -}) +@Mapper(componentModel = "spring", uses = {DateTimeMapper.class}) public interface MeterReadingMapper { /** @@ -55,33 +51,21 @@ public interface MeterReadingMapper { /** * Converts a MeterReadingDto to a MeterReadingEntity. - * Maps all related DTOs to their corresponding entities. - * + * Since MeterReading has no child elements, only audit and relationship fields are managed. + * * @param dto the meter reading DTO * @return the meter reading entity */ @Mapping(target = "id", source = "uuid", qualifiedByName = "stringToUuid") - @Mapping(target = "usagePoint", ignore = true) - @Mapping(target = "readingType", ignore = true) - @Mapping(target = "intervalBlocks", ignore = true) - @Mapping(target = "relatedLinks", ignore = true) - @Mapping(target = "selfLink", ignore = true) - @Mapping(target = "upLink", ignore = true) + @Mapping(target = "created", ignore = true) // Audit field managed by persistence + @Mapping(target = "updated", ignore = true) // Audit field managed by persistence + @Mapping(target = "published", ignore = true) // Audit field managed by persistence + @Mapping(target = "description", ignore = true) // Managed separately + @Mapping(target = "selfLink", ignore = true) // Link managed separately + @Mapping(target = "upLink", ignore = true) // Link managed separately + @Mapping(target = "relatedLinks", ignore = true) // Links managed separately + @Mapping(target = "usagePoint", ignore = true) // Relationship managed separately + @Mapping(target = "readingType", ignore = true) // Relationship managed separately + @Mapping(target = "intervalBlocks", ignore = true) // Relationship managed separately MeterReadingEntity toEntity(MeterReadingDto dto); - - /** - * Updates an existing MeterReadingEntity with data from a MeterReadingDto. - * Useful for merge operations where the entity ID should be preserved. - * - * @param dto the source DTO - * @param entity the target entity to update - */ - @Mapping(target = "id", ignore = true) - @Mapping(target = "usagePoint", ignore = true) - @Mapping(target = "readingType", ignore = true) - @Mapping(target = "intervalBlocks", ignore = true) - @Mapping(target = "relatedLinks", ignore = true) - @Mapping(target = "selfLink", ignore = true) - @Mapping(target = "upLink", ignore = true) - void updateEntity(MeterReadingDto dto, @MappingTarget MeterReadingEntity entity); } \ No newline at end of file diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/MeterReadingRepository.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/MeterReadingRepository.java index 77419fb8..e9c92dce 100755 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/MeterReadingRepository.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/repositories/usage/MeterReadingRepository.java @@ -21,50 +21,20 @@ import org.greenbuttonalliance.espi.common.domain.usage.MeterReadingEntity; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.Optional; import java.util.UUID; @Repository public interface MeterReadingRepository extends JpaRepository { - // JpaRepository provides: save(), findById(), findAll(), deleteById(), etc. - - @Modifying - @Transactional - @Query("DELETE FROM MeterReadingEntity m WHERE m.id = :id") - void deleteById(@Param("id") UUID id); - @Query("SELECT m.id FROM MeterReadingEntity m") List findAllIds(); - // findById is already provided by JpaRepository - // Optional findById(UUID id) is inherited - - // deleteById is already provided by JpaRepository - // void deleteById(UUID id) is inherited - - // Custom method for createOrReplaceByUUID - should be implemented in service layer - - @Query("SELECT m FROM MeterReadingEntity m join m.relatedLinks link WHERE link.href = :href") - List findByRelatedHref(@Param("href") String href); - - @Query("SELECT readingType FROM ReadingTypeEntity readingType WHERE readingType.selfLink.href in (:relatedLinkHrefs)") - List findAllRelated(@Param("relatedLinkHrefs") List relatedLinkHrefs); - @Query("SELECT m.id FROM MeterReadingEntity m WHERE m.usagePoint.id = :usagePointId") List findAllIdsByUsagePointId(@Param("usagePointId") UUID usagePointId); - @Query("SELECT DISTINCT m.id FROM UsagePointEntity u, MeterReadingEntity m WHERE u.retailCustomer.id = :o1Id AND m.usagePoint.id = :o2Id") - List findAllIdsByXpath2(@Param("o1Id") UUID o1Id, @Param("o2Id") UUID o2Id); - - @Query("SELECT DISTINCT m.id FROM UsagePointEntity u, MeterReadingEntity m WHERE u.retailCustomer.id = :o1Id AND m.usagePoint.id = :o2Id AND m.id = :o3Id") - Optional findIdByXpath(@Param("o1Id") UUID o1Id, @Param("o2Id") UUID o2Id, @Param("o3Id") UUID o3Id); - } diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/MeterReadingRepositoryTest.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/MeterReadingRepositoryTest.java index 8fca123b..1b3f7d95 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/MeterReadingRepositoryTest.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/MeterReadingRepositoryTest.java @@ -267,41 +267,6 @@ void shouldFindAllMeterReadingIds() { ); } - @Test - @DisplayName("Should find meter readings by related href") - void shouldFindMeterReadingsByRelatedHref() { - // Arrange - Create a simple meter reading without related links for now - MeterReadingEntity meterReading = TestDataBuilders.createValidMeterReading(); - meterReading.setDescription("Meter Reading for Related Href Test"); - meterReadingRepository.save(meterReading); - flushAndClear(); - - // Act - Test the query method with a non-existent href (should return empty) - List results = meterReadingRepository.findByRelatedHref("/espi/1_1/resource/IntervalBlock/123"); - - // Assert - Should return empty list since no meter reading has this related href - assertThat(results).isEmpty(); - } - - @Test - @DisplayName("Should find all related entities") - void shouldFindAllRelatedEntities() { - // Arrange - List relatedLinkHrefs = List.of( - "/espi/1_1/resource/ReadingType/1", - "/espi/1_1/resource/ReadingType/2", - "/espi/1_1/resource/ReadingType/3" - ); - - // Act - List results = meterReadingRepository.findAllRelated(relatedLinkHrefs); - - // Assert - assertThat(results).isNotNull(); - // Note: This query looks for ReadingType entities with matching self links - // The actual results depend on existing data in the test database - } - @Test @DisplayName("Should find all meter reading IDs by usage point ID") void shouldFindAllMeterReadingIdsByUsagePointId() { @@ -326,73 +291,11 @@ void shouldFindAllMeterReadingIdsByUsagePointId() { assertThat(meterReadingIds).contains(savedMeterReadings.get(0).getId(), savedMeterReadings.get(1).getId()); } - @Test - @DisplayName("Should find all meter reading IDs by xpath2") - void shouldFindAllMeterReadingIdsByXpath2() { - // Arrange - RetailCustomerEntity retailCustomer = TestDataBuilders.createValidRetailCustomer(); - retailCustomer.setUsername("customer@xpath2.com"); - RetailCustomerEntity savedCustomer = retailCustomerRepository.save(retailCustomer); - - UsagePointEntity usagePoint = TestDataBuilders.createValidUsagePoint(); - usagePoint.setDescription("Usage Point for Xpath2"); - usagePoint.setRetailCustomer(savedCustomer); - UsagePointEntity savedUsagePoint = usagePointRepository.save(usagePoint); - - MeterReadingEntity meterReading1 = TestDataBuilders.createValidMeterReadingWithUsagePoint(savedUsagePoint); - meterReading1.setDescription("Meter Reading 1 for Xpath2"); - MeterReadingEntity meterReading2 = TestDataBuilders.createValidMeterReadingWithUsagePoint(savedUsagePoint); - meterReading2.setDescription("Meter Reading 2 for Xpath2"); - - List savedMeterReadings = meterReadingRepository.saveAll(List.of(meterReading1, meterReading2)); - flushAndClear(); - - // Act - List meterReadingIds = meterReadingRepository.findAllIdsByXpath2(savedCustomer.getId(), savedUsagePoint.getId()); - - // Assert - assertThat(meterReadingIds).hasSize(2); - assertThat(meterReadingIds).contains(savedMeterReadings.get(0).getId(), savedMeterReadings.get(1).getId()); - } - - @Test - @DisplayName("Should find meter reading ID by xpath") - void shouldFindMeterReadingIdByXpath() { - // Arrange - RetailCustomerEntity retailCustomer = TestDataBuilders.createValidRetailCustomer(); - retailCustomer.setUsername("customer@xpath.com"); - RetailCustomerEntity savedCustomer = retailCustomerRepository.save(retailCustomer); - - UsagePointEntity usagePoint = TestDataBuilders.createValidUsagePoint(); - usagePoint.setDescription("Usage Point for Xpath"); - usagePoint.setRetailCustomer(savedCustomer); - UsagePointEntity savedUsagePoint = usagePointRepository.save(usagePoint); - - MeterReadingEntity meterReading = TestDataBuilders.createValidMeterReadingWithUsagePoint(savedUsagePoint); - meterReading.setDescription("Meter Reading for Xpath"); - MeterReadingEntity savedMeterReading = meterReadingRepository.save(meterReading); - flushAndClear(); - - // Act - Optional result = meterReadingRepository.findIdByXpath( - savedCustomer.getId(), - savedUsagePoint.getId(), - savedMeterReading.getId() - ); - - // Assert - assertThat(result).isPresent(); - assertThat(result.get()).isEqualTo(savedMeterReading.getId()); - } - @Test @DisplayName("Should handle empty results gracefully") void shouldHandleEmptyResultsGracefully() { // Act & Assert - assertThat(meterReadingRepository.findByRelatedHref("nonexistent-href")).isEmpty(); assertThat(meterReadingRepository.findAllIdsByUsagePointId(UUID.randomUUID())).isEmpty(); - assertThat(meterReadingRepository.findAllIdsByXpath2(UUID.randomUUID(), UUID.randomUUID())).isEmpty(); - assertThat(meterReadingRepository.findIdByXpath(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID())).isEmpty(); } }