diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/BillingChargeSource.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/BillingChargeSource.java new file mode 100644 index 00000000..2c48c1dc --- /dev/null +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/BillingChargeSource.java @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) 2025 Green Button Alliance, Inc. + * + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.greenbuttonalliance.espi.common.domain.common; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * Embeddable value object for BillingChargeSource. + *

+ * Information about the source of billing charge. + * Per ESPI 4.0 XSD (espi.xsd:1628-1643), BillingChargeSource extends Object + * and contains a single agencyName field. + *

+ * Embedded within UsageSummary entity. + */ +@Embeddable +@Data +@NoArgsConstructor +@AllArgsConstructor +public class BillingChargeSource implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * Name of the billing source agency. + * Maximum length 256 characters per String256 type. + */ + @Column(name = "billing_charge_source_agency_name", length = 256) + private String agencyName; + + /** + * Checks if this billing charge source has a value. + * + * @return true if agency name is present + */ + public boolean hasValue() { + return agencyName != null && !agencyName.trim().isEmpty(); + } +} diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/TariffRiderRefEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/TariffRiderRefEntity.java new file mode 100644 index 00000000..5efcc208 --- /dev/null +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/common/TariffRiderRefEntity.java @@ -0,0 +1,156 @@ +/* + * + * Copyright (c) 2025 Green Button Alliance, Inc. + * + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.greenbuttonalliance.espi.common.domain.common; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.greenbuttonalliance.espi.common.domain.usage.UsageSummaryEntity; +import org.hibernate.proxy.HibernateProxy; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Objects; + +/** + * Pure JPA/Hibernate entity for TariffRiderRef without JAXB concerns. + *

+ * Represents a reference to a tariff rider applied to a usage summary. + * TariffRiderRef extends Object (not IdentifiedObject) per ESPI 4.0 XSD (espi.xsd:1602), + * so it uses Long ID with auto-increment instead of UUID. + *

+ * Part of TariffRiderRefs collection in UsageSummary. + */ +@Entity +@Table(name = "tariff_rider_refs", indexes = { + @Index(name = "idx_tariff_rider_ref_usage_summary", columnList = "usage_summary_id"), + @Index(name = "idx_tariff_rider_ref_rider_type", columnList = "rider_type"), + @Index(name = "idx_tariff_rider_ref_enrollment_status", columnList = "enrollment_status") +}) +@Getter +@Setter +@NoArgsConstructor +public class TariffRiderRefEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * Primary key - Long ID with auto-increment. + * TariffRiderRef extends Object, not IdentifiedObject. + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(updatable = false, nullable = false) + private Long id; + + /** + * Type of tariff rider. + * Rate options applied to the base tariff profile. + * Required field per ESPI 4.0 XSD. + */ + @Column(name = "rider_type", length = 256, nullable = false) + @NotBlank(message = "Rider type cannot be blank") + private String riderType; + + /** + * Retail customer's tariff rider enrollment status. + * EnrollmentStatus enumeration values: "unenrolled", "enrolled", "enrolledPending". + * Required field per ESPI 4.0 XSD. + */ + @Column(name = "enrollment_status", length = 32, nullable = false) + @NotBlank(message = "Enrollment status cannot be blank") + private String enrollmentStatus; + + /** + * Effective date of retail customer's tariff rider enrollment status. + * Stored as epoch seconds (TimeType in ESPI). + * Required field per ESPI 4.0 XSD. + */ + @Column(name = "effective_date", nullable = false) + @NotNull(message = "Effective date cannot be null") + private Long effectiveDate; + + /** + * Parent usage summary that this tariff rider ref belongs to. + * Many tariff rider refs can belong to one usage summary. + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "usage_summary_id") + private UsageSummaryEntity usageSummary; + + /** + * Constructor with required fields. + * + * @param riderType the type of tariff rider + * @param enrollmentStatus the enrollment status ("unenrolled", "enrolled", "enrolledPending") + * @param effectiveDate the effective date as epoch seconds + */ + public TariffRiderRefEntity(String riderType, String enrollmentStatus, Long effectiveDate) { + this.riderType = riderType; + this.enrollmentStatus = enrollmentStatus; + this.effectiveDate = effectiveDate; + } + + /** + * Validates the tariff rider ref has all required fields. + * + * @return true if valid + */ + public boolean isValid() { + return riderType != null && !riderType.trim().isEmpty() && + enrollmentStatus != null && !enrollmentStatus.trim().isEmpty() && + effectiveDate != null; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy + ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() + : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy + ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() + : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + TariffRiderRefEntity that = (TariffRiderRefEntity) o; + return getId() != null && Objects.equals(getId(), that.getId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy + ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() + : getClass().hashCode(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + + "id = " + id + ", " + + "riderType = " + riderType + ", " + + "enrollmentStatus = " + enrollmentStatus + ", " + + "effectiveDate = " + effectiveDate + ")"; + } +} diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsageSummaryEntity.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsageSummaryEntity.java index f27d1374..a4a82558 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsageSummaryEntity.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/domain/usage/UsageSummaryEntity.java @@ -23,9 +23,11 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.greenbuttonalliance.espi.common.domain.common.BillingChargeSource; import org.greenbuttonalliance.espi.common.domain.common.DateTimeInterval; import org.greenbuttonalliance.espi.common.domain.common.IdentifiedObject; import org.greenbuttonalliance.espi.common.domain.common.SummaryMeasurement; +import org.greenbuttonalliance.espi.common.domain.common.TariffRiderRefEntity; import org.hibernate.proxy.HibernateProxy; import java.util.ArrayList; @@ -48,6 +50,19 @@ public class UsageSummaryEntity extends IdentifiedObject { private static final long serialVersionUID = 1L; + // ========== XSD Order: Fields 1-4 (Simple Types) ========== + + /** + * Billing period for this summary. + * Time interval covered by the billing period. + */ + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "start", column = @Column(name = "billing_period_start")), + @AttributeOverride(name = "duration", column = @Column(name = "billing_period_duration")) + }) + private DateTimeInterval billingPeriod; + /** * Bill amount for the last billing period. * Monetary value representing the total bill. @@ -69,48 +84,25 @@ public class UsageSummaryEntity extends IdentifiedObject { @Column(name = "cost_additional_last_period") private Long costAdditionalLastPeriod; - /** - * Currency code for monetary values. - * ISO 4217 currency codes (USD, EUR, etc.). - */ - @Column(name = "currency", length = 3) - private String currency; + // ========== XSD Order: Field 5 (Collection) ========== /** - * Quality indicator for the reading data. - * Describes the reliability and accuracy of the measurements. + * Additional cost details for the last billing period. + * Line items breaking down additional charges. */ - @Column(name = "quality_of_reading", length = 50) - private String qualityOfReading; + @OneToMany(mappedBy = "usageSummary", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + private List costAdditionalDetailLastPeriod = new ArrayList<>(); - /** - * Timestamp indicating when the status was last updated. - * Unix timestamp format. - */ - @Column(name = "status_timestamp") - private Long statusTimeStamp; + // ========== XSD Order: Field 6 (Simple Type) ========== /** - * Billing period for this summary. - * Time interval covered by the billing period. + * Currency code for monetary values. + * ISO 4217 currency codes (USD, EUR, etc.). */ - @Embedded - @AttributeOverrides({ - @AttributeOverride(name = "start", column = @Column(name = "billing_period_start")), - @AttributeOverride(name = "duration", column = @Column(name = "billing_period_duration")) - }) - private DateTimeInterval billingPeriod; + @Column(name = "currency", length = 3) + private String currency; - /** - * Ratchet demand period. - * Time period for the highest demand that sets the ratchet. - */ - @Embedded - @AttributeOverrides({ - @AttributeOverride(name = "start", column = @Column(name = "ratchet_demand_period_start")), - @AttributeOverride(name = "duration", column = @Column(name = "ratchet_demand_period_duration")) - }) - private DateTimeInterval ratchetDemandPeriod; + // ========== XSD Order: Fields 7-15 (SummaryMeasurements) ========== /** * Overall consumption for the last billing period. @@ -229,6 +221,17 @@ public class UsageSummaryEntity extends IdentifiedObject { }) private SummaryMeasurement previousDayOverallConsumption; + // ========== XSD Order: Field 16 (Simple Type) ========== + + /** + * Quality indicator for the reading data. + * Describes the reliability and accuracy of the measurements. + */ + @Column(name = "quality_of_reading", length = 50) + private String qualityOfReading; + + // ========== XSD Order: Field 17 (SummaryMeasurement) ========== + /** * Ratchet demand measurement. * Highest demand that establishes billing demand. @@ -243,6 +246,79 @@ public class UsageSummaryEntity extends IdentifiedObject { }) private SummaryMeasurement ratchetDemand; + // ========== XSD Order: Field 18 (DateTimeInterval) ========== + + /** + * Ratchet demand period. + * Time period for the highest demand that sets the ratchet. + */ + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "start", column = @Column(name = "ratchet_demand_period_start")), + @AttributeOverride(name = "duration", column = @Column(name = "ratchet_demand_period_duration")) + }) + private DateTimeInterval ratchetDemandPeriod; + + // ========== XSD Order: Field 19 (Simple Type - REQUIRED) ========== + + /** + * Timestamp indicating when the status was last updated. + * Unix timestamp format. + * REQUIRED field per ESPI 4.0 XSD. + */ + @Column(name = "status_timestamp") + private Long statusTimeStamp; + + // ========== XSD Order: Field 20 (Simple Type - NEW) ========== + + /** + * Commodity being measured. + * ServiceKind enumeration value (e.g., 0=electricity, 1=gas, 2=water). + */ + @Column(name = "commodity") + private Integer commodity; + + // ========== XSD Order: Field 21 (Simple Type - NEW) ========== + + /** + * Tariff profile identifier. + * Identifies the tariff or rate schedule applied. + */ + @Column(name = "tariff_profile", length = 256) + private String tariffProfile; + + // ========== XSD Order: Field 22 (Simple Type - NEW) ========== + + /** + * Read cycle identifier. + * Identifies the meter reading schedule or cycle. + */ + @Column(name = "read_cycle", length = 256) + private String readCycle; + + // ========== XSD Order: Field 23 (Collection - NEW) ========== + + /** + * References to tariff riders applied to this usage summary. + * Tariff riders are rate options applied to the base tariff. + */ + @OneToMany(mappedBy = "usageSummary", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + private List tariffRiderRefs = new ArrayList<>(); + + // ========== XSD Order: Field 24 (Embedded - NEW) ========== + + /** + * Information about the source of billing charges. + * Agency or system that generated the billing information. + */ + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "agencyName", column = @Column(name = "billing_charge_source_agency_name", length = 256)) + }) + private BillingChargeSource billingChargeSource; + + // ========== XSD Order: Field 25 (Relationship - Keep at end) ========== + /** * Usage point that this summary belongs to. * Many summaries can belong to one usage point. @@ -251,13 +327,6 @@ public class UsageSummaryEntity extends IdentifiedObject { @JoinColumn(name = "usage_point_id") private UsagePointEntity usagePoint; - /** - * Additional cost details for the last billing period. - * Line items breaking down additional charges. - */ - @OneToMany(mappedBy = "usageSummary", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) - private List costAdditionalDetailLastPeriod = new ArrayList<>(); - /** * Constructor with billing period and basic bill amounts. * @@ -287,7 +356,7 @@ public void addCostAdditionalDetailLastPeriod(LineItemEntity lineItem) { /** * Removes a line item from additional cost details. * Clears the bidirectional relationship. - * + * * @param lineItem the line item to remove */ public void removeCostAdditionalDetailLastPeriod(LineItemEntity lineItem) { @@ -297,6 +366,32 @@ public void removeCostAdditionalDetailLastPeriod(LineItemEntity lineItem) { } } + /** + * Adds a tariff rider reference to this usage summary. + * Sets up the bidirectional relationship. + * + * @param tariffRiderRef the tariff rider ref to add + */ + public void addTariffRiderRef(TariffRiderRefEntity tariffRiderRef) { + if (tariffRiderRef != null) { + this.tariffRiderRefs.add(tariffRiderRef); + tariffRiderRef.setUsageSummary(this); + } + } + + /** + * Removes a tariff rider reference from this usage summary. + * Clears the bidirectional relationship. + * + * @param tariffRiderRef the tariff rider ref to remove + */ + public void removeTariffRiderRef(TariffRiderRefEntity tariffRiderRef) { + if (tariffRiderRef != null) { + this.tariffRiderRefs.remove(tariffRiderRef); + tariffRiderRef.setUsageSummary(null); + } + } + // Note: Simple setUsagePoint setter is generated by Lombok @Data // Complex bidirectional relationship management removed - handled by DataCustodian/ThirdParty applications @@ -347,25 +442,20 @@ protected String generateDefaultUpHref() { /** * Merges data from another UsageSummaryEntity. * Updates all summary data and embedded measurements. - * + * * @param other the other usage summary entity to merge from */ public void merge(UsageSummaryEntity other) { if (other != null) { super.merge(other); - + // Update billing and cost information + this.billingPeriod = other.billingPeriod; this.billLastPeriod = other.billLastPeriod; this.billToDate = other.billToDate; this.costAdditionalLastPeriod = other.costAdditionalLastPeriod; this.currency = other.currency; - this.qualityOfReading = other.qualityOfReading; - this.statusTimeStamp = other.statusTimeStamp; - - // Update time intervals - this.billingPeriod = other.billingPeriod; - this.ratchetDemandPeriod = other.ratchetDemandPeriod; - + // Update embedded measurements this.overallConsumptionLastPeriod = other.overallConsumptionLastPeriod; this.currentBillingPeriodOverAllConsumption = other.currentBillingPeriodOverAllConsumption; @@ -376,8 +466,19 @@ public void merge(UsageSummaryEntity other) { this.previousDayLastYearOverallConsumption = other.previousDayLastYearOverallConsumption; this.previousDayNetConsumption = other.previousDayNetConsumption; this.previousDayOverallConsumption = other.previousDayOverallConsumption; + + // Update quality and demand information + this.qualityOfReading = other.qualityOfReading; this.ratchetDemand = other.ratchetDemand; - + this.ratchetDemandPeriod = other.ratchetDemandPeriod; + this.statusTimeStamp = other.statusTimeStamp; + + // Update new ESPI 4.0 fields + this.commodity = other.commodity; + this.tariffProfile = other.tariffProfile; + this.readCycle = other.readCycle; + this.billingChargeSource = other.billingChargeSource; + // Replace cost detail line items with bidirectional setup this.costAdditionalDetailLastPeriod.clear(); if (other.costAdditionalDetailLastPeriod != null) { @@ -385,7 +486,15 @@ public void merge(UsageSummaryEntity other) { addCostAdditionalDetailLastPeriod(lineItem); } } - + + // Replace tariff rider refs with bidirectional setup + this.tariffRiderRefs.clear(); + if (other.tariffRiderRefs != null) { + for (TariffRiderRefEntity tariffRiderRef : other.tariffRiderRefs) { + addTariffRiderRef(tariffRiderRef); + } + } + // Update usage point if provided if (other.usagePoint != null) { this.usagePoint = other.usagePoint; @@ -398,13 +507,19 @@ public void merge(UsageSummaryEntity other) { */ public void unlink() { clearRelatedLinks(); - + // Clear cost detail line items for (LineItemEntity lineItem : new ArrayList<>(costAdditionalDetailLastPeriod)) { removeCostAdditionalDetailLastPeriod(lineItem); } costAdditionalDetailLastPeriod.clear(); - + + // Clear tariff rider refs + for (TariffRiderRefEntity tariffRiderRef : new ArrayList<>(tariffRiderRefs)) { + removeTariffRiderRef(tariffRiderRef); + } + tariffRiderRefs.clear(); + // Clear relationships with simple field assignment this.usagePoint = null; } @@ -573,14 +688,11 @@ public final int hashCode() { public String toString() { return getClass().getSimpleName() + "(" + "id = " + getId() + ", " + + "billingPeriod = " + getBillingPeriod() + ", " + "billLastPeriod = " + getBillLastPeriod() + ", " + "billToDate = " + getBillToDate() + ", " + "costAdditionalLastPeriod = " + getCostAdditionalLastPeriod() + ", " + "currency = " + getCurrency() + ", " + - "qualityOfReading = " + getQualityOfReading() + ", " + - "statusTimeStamp = " + getStatusTimeStamp() + ", " + - "billingPeriod = " + getBillingPeriod() + ", " + - "ratchetDemandPeriod = " + getRatchetDemandPeriod() + ", " + "overallConsumptionLastPeriod = " + getOverallConsumptionLastPeriod() + ", " + "currentBillingPeriodOverAllConsumption = " + getCurrentBillingPeriodOverAllConsumption() + ", " + "currentDayLastYearNetConsumption = " + getCurrentDayLastYearNetConsumption() + ", " + @@ -590,7 +702,14 @@ public String toString() { "previousDayLastYearOverallConsumption = " + getPreviousDayLastYearOverallConsumption() + ", " + "previousDayNetConsumption = " + getPreviousDayNetConsumption() + ", " + "previousDayOverallConsumption = " + getPreviousDayOverallConsumption() + ", " + + "qualityOfReading = " + getQualityOfReading() + ", " + "ratchetDemand = " + getRatchetDemand() + ", " + + "ratchetDemandPeriod = " + getRatchetDemandPeriod() + ", " + + "statusTimeStamp = " + getStatusTimeStamp() + ", " + + "commodity = " + getCommodity() + ", " + + "tariffProfile = " + getTariffProfile() + ", " + + "readCycle = " + getReadCycle() + ", " + + "billingChargeSource = " + getBillingChargeSource() + ", " + "description = " + getDescription() + ", " + "created = " + getCreated() + ", " + "updated = " + getUpdated() + ", " + diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/BillingChargeSourceDto.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/BillingChargeSourceDto.java new file mode 100644 index 00000000..1caf4549 --- /dev/null +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/BillingChargeSourceDto.java @@ -0,0 +1,74 @@ +/* + * + * Copyright (c) 2025 Green Button Alliance, Inc. + * + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.greenbuttonalliance.espi.common.dto; + +import jakarta.xml.bind.annotation.*; + +/** + * BillingChargeSource DTO record for JAXB XML marshalling/unmarshalling. + *

+ * Information about the source of billing charge. + * Per ESPI 4.0 XSD (espi.xsd:1628-1643), BillingChargeSource extends Object + * and contains a single agencyName field. + *

+ * Embedded within UsageSummary DTO. + */ +@XmlAccessorType(XmlAccessType.PROPERTY) +@XmlType(name = "BillingChargeSource", namespace = "http://naesb.org/espi", propOrder = { + "agencyName" +}) +public record BillingChargeSourceDto( + String agencyName +) { + + /** + * Name of the billing source agency. + * Maximum length 256 characters per String256 type. + */ + @XmlElement(name = "agencyName") + public String getAgencyName() { + return agencyName; + } + + /** + * Default constructor for JAXB. + */ + public BillingChargeSourceDto() { + this(null); + } + + /** + * Constructor with agency name. + * + * @param agencyName the name of the billing source agency + */ + public BillingChargeSourceDto(String agencyName) { + this.agencyName = agencyName; + } + + /** + * Checks if this billing charge source has a value. + * + * @return true if agency name is present + */ + public boolean hasValue() { + return agencyName != null && !agencyName.trim().isEmpty(); + } +} diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/UsageSummaryDto.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/UsageSummaryDto.java index 86e768b4..25e203e0 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/UsageSummaryDto.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/dto/usage/UsageSummaryDto.java @@ -19,6 +19,8 @@ package org.greenbuttonalliance.espi.common.dto.usage; +import org.greenbuttonalliance.espi.common.dto.BillingChargeSourceDto; +import org.greenbuttonalliance.espi.common.dto.SummaryMeasurementDto; import org.greenbuttonalliance.espi.common.dto.atom.LinkDto; import jakarta.xml.bind.annotation.*; @@ -27,79 +29,344 @@ /** * UsageSummary DTO record for JAXB XML marshalling/unmarshalling. - * + *

* Represents aggregated usage data for a usage point. - * Supports Atom protocol XML wrapping. + * Per ESPI 4.0 XSD (espi.xsd:806-939), UsageSummary extends IdentifiedObject + * and contains billing information, consumption summaries, and tariff details. + *

+ * Supports Atom protocol XML wrapping with published, updated, and link metadata. */ @XmlRootElement(name = "UsageSummary", namespace = "http://naesb.org/espi") -@XmlAccessorType(XmlAccessType.FIELD) +@XmlAccessorType(XmlAccessType.PROPERTY) @XmlType(name = "UsageSummary", namespace = "http://naesb.org/espi", propOrder = { - "id", "uuid", "published", "updated", "selfLink", "upLink", "relatedLinks", - "description", "billingPeriod", "billLastPeriod", "billToDate", - "costAdditionalLastPeriod", "currency", "qualityOfReading", "statusTimeStamp" + "description", "billingPeriod", "billLastPeriod", "billToDate", + "costAdditionalLastPeriod", "costAdditionalDetailLastPeriod", "currency", + "overallConsumptionLastPeriod", "currentBillingPeriodOverAllConsumption", + "currentDayLastYearNetConsumption", "currentDayNetConsumption", + "currentDayOverallConsumption", "peakDemand", "previousDayLastYearOverallConsumption", + "previousDayNetConsumption", "previousDayOverallConsumption", "qualityOfReading", + "ratchetDemand", "ratchetDemandPeriod", "statusTimeStamp", "commodity", + "tariffProfile", "readCycle", "tariffRiderRefs", "billingChargeSource" }) public record UsageSummaryDto( - + @XmlTransient Long id, - + @XmlAttribute(name = "mRID") String uuid, - - @XmlElement(name = "published") + + @XmlTransient OffsetDateTime published, - - @XmlElement(name = "updated") + + @XmlTransient OffsetDateTime updated, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") - @XmlElementWrapper(name = "links", namespace = "http://www.w3.org/2005/Atom") - List relatedLinks, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") + + @XmlTransient LinkDto selfLink, - - @XmlElement(name = "link", namespace = "http://www.w3.org/2005/Atom") + + @XmlTransient LinkDto upLink, - - @XmlElement(name = "description") + + @XmlTransient + List relatedLinks, + String description, - - @XmlElement(name = "billingPeriod") DateTimeIntervalDto billingPeriod, - - @XmlElement(name = "billLastPeriod") Long billLastPeriod, - - @XmlElement(name = "billToDate") Long billToDate, - - @XmlElement(name = "costAdditionalLastPeriod") Long costAdditionalLastPeriod, - - @XmlElement(name = "currency") + List costAdditionalDetailLastPeriod, String currency, - - @XmlElement(name = "qualityOfReading") + SummaryMeasurementDto overallConsumptionLastPeriod, + SummaryMeasurementDto currentBillingPeriodOverAllConsumption, + SummaryMeasurementDto currentDayLastYearNetConsumption, + SummaryMeasurementDto currentDayNetConsumption, + SummaryMeasurementDto currentDayOverallConsumption, + SummaryMeasurementDto peakDemand, + SummaryMeasurementDto previousDayLastYearOverallConsumption, + SummaryMeasurementDto previousDayNetConsumption, + SummaryMeasurementDto previousDayOverallConsumption, String qualityOfReading, - - @XmlElement(name = "statusTimeStamp") - OffsetDateTime statusTimeStamp + SummaryMeasurementDto ratchetDemand, + DateTimeIntervalDto ratchetDemandPeriod, + Long statusTimeStamp, + Integer commodity, + String tariffProfile, + String readCycle, + TariffRiderRefsDto tariffRiderRefs, + BillingChargeSourceDto billingChargeSource ) { - + + /** + * Description of the usage summary. + * Inherited from IdentifiedObject. + */ + @XmlElement(name = "description") + public String getDescription() { + return description; + } + + /** + * The billing period to which the included measurements apply. + * May also be an off-bill arbitrary period. + */ + @XmlElement(name = "billingPeriod") + public DateTimeIntervalDto getBillingPeriod() { + return billingPeriod; + } + + /** + * The amount of the bill for the referenced billingPeriod in hundred-thousandths + * of the currency specified in the ReadingType (e.g., 840 = USD). + */ + @XmlElement(name = "billLastPeriod") + public Long getBillLastPeriod() { + return billLastPeriod; + } + + /** + * If the summary contains data from a current period beyond the end of the + * referenced billingPeriod, the bill amount as of the date the summary is generated, + * in hundred-thousandths of the currency. + */ + @XmlElement(name = "billToDate") + public Long getBillToDate() { + return billToDate; + } + + /** + * Additional charges from the referenced billingPeriod, in hundred-thousandths + * of the currency specified in the ReadingType. + */ + @XmlElement(name = "costAdditionalLastPeriod") + public Long getCostAdditionalLastPeriod() { + return costAdditionalLastPeriod; + } + + /** + * Additional charges from the referenced billingPeriod which in total add up + * to costAdditionalLastPeriod. + * Extension field. + */ + @XmlElement(name = "costAdditionalDetailLastPeriod") + public List getCostAdditionalDetailLastPeriod() { + return costAdditionalDetailLastPeriod; + } + + /** + * The ISO 4217 code indicating the currency applicable to the bill amounts + * in the summary. + */ + @XmlElement(name = "currency") + public String getCurrency() { + return currency; + } + + /** + * The amount of energy consumed for the referenced billingPeriod. + * Extension field. + */ + @XmlElement(name = "overallConsumptionLastPeriod") + public SummaryMeasurementDto getOverallConsumptionLastPeriod() { + return overallConsumptionLastPeriod; + } + + /** + * If the summary contains data from a current period beyond the end of the + * referenced billingPeriod, the total consumption for the billing period. + */ + @XmlElement(name = "currentBillingPeriodOverAllConsumption") + public SummaryMeasurementDto getCurrentBillingPeriodOverAllConsumption() { + return currentBillingPeriodOverAllConsumption; + } + + /** + * If the summary contains data from a current period beyond the end of the + * referenced billingPeriod, the amount of energy consumed one year ago interpreted + * as same day of week same week of year (see ISO 8601) based on the day of the statusTimeStamp. + */ + @XmlElement(name = "currentDayLastYearNetConsumption") + public SummaryMeasurementDto getCurrentDayLastYearNetConsumption() { + return currentDayLastYearNetConsumption; + } + + /** + * If the summary contains data from a current period beyond the end of the + * referenced billingPeriod, net consumption for the current day (delivered - received) + * based on the day of the statusTimeStamp. + */ + @XmlElement(name = "currentDayNetConsumption") + public SummaryMeasurementDto getCurrentDayNetConsumption() { + return currentDayNetConsumption; + } + + /** + * If the summary contains data from a current period beyond the end of the + * referenced billingPeriod, overall energy consumption for the current day, + * based on the day of the statusTimeStamp. + */ + @XmlElement(name = "currentDayOverallConsumption") + public SummaryMeasurementDto getCurrentDayOverallConsumption() { + return currentDayOverallConsumption; + } + + /** + * If the summary contains data from a current period beyond the end of the + * referenced billingPeriod, peak demand recorded for the current period. + */ + @XmlElement(name = "peakDemand") + public SummaryMeasurementDto getPeakDemand() { + return peakDemand; + } + + /** + * If the summary contains data from a current period beyond the end of the + * referenced billingPeriod, the amount of energy consumed on the previous day + * one year ago interpreted as same day of week same week of year (see ISO 8601) + * based on the day of the statusTimestamp. + */ + @XmlElement(name = "previousDayLastYearOverallConsumption") + public SummaryMeasurementDto getPreviousDayLastYearOverallConsumption() { + return previousDayLastYearOverallConsumption; + } + + /** + * If the summary contains data from a current period beyond the end of the + * referenced billingPeriod, net consumption for the previous day relative to + * the day of the statusTimestamp. + */ + @XmlElement(name = "previousDayNetConsumption") + public SummaryMeasurementDto getPreviousDayNetConsumption() { + return previousDayNetConsumption; + } + + /** + * If the summary contains data from a current period beyond the end of the + * referenced billingPeriod, the total consumption for the previous day based + * on the day of the statusTimestamp. + */ + @XmlElement(name = "previousDayOverallConsumption") + public SummaryMeasurementDto getPreviousDayOverallConsumption() { + return previousDayOverallConsumption; + } + + /** + * Indication of the quality of the summary readings. + * QualityOfReading enumeration value. + */ + @XmlElement(name = "qualityOfReading") + public String getQualityOfReading() { + return qualityOfReading; + } + + /** + * If the summary contains data from a current period beyond the end of the + * referenced billingPeriod, the current ratchet demand value for the ratchet + * demand over the ratchetDemandPeriod. + */ + @XmlElement(name = "ratchetDemand") + public SummaryMeasurementDto getRatchetDemand() { + return ratchetDemand; + } + + /** + * The period over which the ratchet demand applies. + */ + @XmlElement(name = "ratchetDemandPeriod") + public DateTimeIntervalDto getRatchetDemandPeriod() { + return ratchetDemandPeriod; + } + + /** + * Date/Time status of this UsageSummary. + * Required field - TimeType (epoch seconds). + */ + @XmlElement(name = "statusTimeStamp") + public Long getStatusTimeStamp() { + return statusTimeStamp; + } + + /** + * The commodity for this summary report. + * CommodityKind enumeration value. + * Extension field. + */ + @XmlElement(name = "commodity") + public Integer getCommodity() { + return commodity; + } + + /** + * A schedule of charges; structure associated with Tariff that allows the + * definition of complex tariff structures such as step and time of use. + * Extension field, maximum length 256 characters. + */ + @XmlElement(name = "tariffProfile") + public String getTariffProfile() { + return tariffProfile; + } + + /** + * Cycle day on which the meter for this usage point will normally be read. + * Usually correlated with the billing cycle. + * Extension field, maximum length 256 characters. + */ + @XmlElement(name = "readCycle") + public String getReadCycle() { + return readCycle; + } + + /** + * List of rate options applied to the base tariff profile. + * Extension field. + */ + @XmlElement(name = "tariffRiderRefs") + public TariffRiderRefsDto getTariffRiderRefs() { + return tariffRiderRefs; + } + + /** + * Source of Billing Charge. + * Extension field. + */ + @XmlElement(name = "billingChargeSource") + public BillingChargeSourceDto getBillingChargeSource() { + return billingChargeSource; + } + /** * Default constructor for JAXB. */ public UsageSummaryDto() { this(null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null); } - + /** * Minimal constructor for basic usage summary data. + * + * @param uuid the resource identifier (mRID) + * @param statusTimeStamp the status timestamp (required) + */ + public UsageSummaryDto(String uuid, Long statusTimeStamp) { + this(null, uuid, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, + null, null, statusTimeStamp, null, null, null, null, null); + } + + /** + * Constructor with billing period and status timestamp. + * + * @param uuid the resource identifier (mRID) + * @param billingPeriod the billing period + * @param statusTimeStamp the status timestamp (required) */ - public UsageSummaryDto(String uuid) { - this(null, uuid, null, null, null, null, null, null, - null, null, null, null, null, null, null); + public UsageSummaryDto(String uuid, DateTimeIntervalDto billingPeriod, Long statusTimeStamp) { + this(null, uuid, null, null, null, null, null, null, billingPeriod, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, + null, null, statusTimeStamp, null, null, null, null, null); } -} \ No newline at end of file +} diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/TariffRiderRefMapper.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/TariffRiderRefMapper.java new file mode 100644 index 00000000..bb44ac3e --- /dev/null +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/TariffRiderRefMapper.java @@ -0,0 +1,106 @@ +/* + * + * Copyright (c) 2025 Green Button Alliance, Inc. + * + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.greenbuttonalliance.espi.common.mapper.usage; + +import org.greenbuttonalliance.espi.common.domain.common.TariffRiderRefEntity; +import org.greenbuttonalliance.espi.common.dto.usage.TariffRiderRefDto; +import org.greenbuttonalliance.espi.common.dto.usage.TariffRiderRefsDto; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +import java.util.List; + +/** + * MapStruct mapper for converting between TariffRiderRefEntity and TariffRiderRefDto. + *

+ * TariffRiderRef extends Object (not IdentifiedObject) in ESPI 4.0, so it does not + * have Atom links or timestamps - only business data fields. + *

+ * Also provides conversion methods for TariffRiderRefs wrapper DTO which contains + * a collection of TariffRiderRef items. + */ +@Mapper(componentModel = "spring") +public interface TariffRiderRefMapper { + + /** + * Converts a TariffRiderRefEntity to a TariffRiderRefDto. + * + * @param entity the tariff rider ref entity + * @return the tariff rider ref DTO + */ + TariffRiderRefDto toDto(TariffRiderRefEntity entity); + + /** + * Converts a TariffRiderRefDto to a TariffRiderRefEntity. + * + * @param dto the tariff rider ref DTO + * @return the tariff rider ref entity + */ + @Mapping(target = "id", ignore = true) + @Mapping(target = "usageSummary", ignore = true) + TariffRiderRefEntity toEntity(TariffRiderRefDto dto); + + /** + * Converts a list of TariffRiderRefDto to a list of TariffRiderRefEntity. + * + * @param dtos the list of tariff rider ref DTOs + * @return the list of tariff rider ref entities + */ + List toEntityList(List dtos); + + /** + * Converts a list of TariffRiderRefEntity to a list of TariffRiderRefDto. + * + * @param entities the list of tariff rider ref entities + * @return the list of tariff rider ref DTOs + */ + List toDtoList(List entities); + + /** + * Converts a TariffRiderRefsDto wrapper to a list of TariffRiderRefEntity. + * Extracts the list from the wrapper DTO. + * + * @param refsDto the wrapper DTO containing tariff rider refs + * @return the list of tariff rider ref entities, or null if input is null + */ + @Named("tariffRiderRefsDtoToEntityList") + default List tariffRiderRefsDtoToEntityList(TariffRiderRefsDto refsDto) { + if (refsDto == null) { + return null; + } + return toEntityList(refsDto.tariffRiderRefs()); + } + + /** + * Converts a list of TariffRiderRefEntity to a TariffRiderRefsDto wrapper. + * Wraps the list in the TariffRiderRefsDto container. + * + * @param entities the list of tariff rider ref entities + * @return the wrapper DTO containing tariff rider refs, or null if input is null + */ + @Named("entityListToTariffRiderRefsDto") + default TariffRiderRefsDto entityListToTariffRiderRefsDto(List entities) { + if (entities == null) { + return null; + } + return new TariffRiderRefsDto(toDtoList(entities)); + } +} diff --git a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/UsageSummaryMapper.java b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/UsageSummaryMapper.java index 25c155fd..2842a1d8 100644 --- a/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/UsageSummaryMapper.java +++ b/openespi-common/src/main/java/org/greenbuttonalliance/espi/common/mapper/usage/UsageSummaryMapper.java @@ -24,6 +24,7 @@ import org.greenbuttonalliance.espi.common.mapper.BaseIdentifiedObjectMapper; import org.greenbuttonalliance.espi.common.mapper.BaseMapperUtils; import org.greenbuttonalliance.espi.common.mapper.DateTimeMapper; +import org.greenbuttonalliance.espi.common.mapper.SummaryMeasurementMapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; @@ -37,14 +38,18 @@ @Mapper(componentModel = "spring", uses = { DateTimeMapper.class, BaseMapperUtils.class, - DateTimeIntervalMapper.class + DateTimeIntervalMapper.class, + SummaryMeasurementMapper.class, + LineItemMapper.class, + TariffRiderRefMapper.class }) public interface UsageSummaryMapper { /** * Converts a UsageSummaryEntity to a UsageSummaryDto. - * Maps usage summary data including billing period and cost information. - * + * Maps usage summary data including billing period, cost information, consumption summaries, + * and tariff details per ESPI 4.0 specification. + * * @param entity the usage summary entity * @return the usage summary DTO */ @@ -60,15 +65,33 @@ public interface UsageSummaryMapper { @Mapping(target = "billLastPeriod", source = "billLastPeriod") @Mapping(target = "billToDate", source = "billToDate") @Mapping(target = "costAdditionalLastPeriod", source = "costAdditionalLastPeriod") + @Mapping(target = "costAdditionalDetailLastPeriod", source = "costAdditionalDetailLastPeriod") @Mapping(target = "currency", source = "currency") + @Mapping(target = "overallConsumptionLastPeriod", source = "overallConsumptionLastPeriod") + @Mapping(target = "currentBillingPeriodOverAllConsumption", source = "currentBillingPeriodOverAllConsumption") + @Mapping(target = "currentDayLastYearNetConsumption", source = "currentDayLastYearNetConsumption") + @Mapping(target = "currentDayNetConsumption", source = "currentDayNetConsumption") + @Mapping(target = "currentDayOverallConsumption", source = "currentDayOverallConsumption") + @Mapping(target = "peakDemand", source = "peakDemand") + @Mapping(target = "previousDayLastYearOverallConsumption", source = "previousDayLastYearOverallConsumption") + @Mapping(target = "previousDayNetConsumption", source = "previousDayNetConsumption") + @Mapping(target = "previousDayOverallConsumption", source = "previousDayOverallConsumption") @Mapping(target = "qualityOfReading", source = "qualityOfReading") - @Mapping(target = "statusTimeStamp", source = "statusTimeStamp", qualifiedByName = "longToOffset") + @Mapping(target = "ratchetDemand", source = "ratchetDemand") + @Mapping(target = "ratchetDemandPeriod", source = "ratchetDemandPeriod") + @Mapping(target = "statusTimeStamp", source = "statusTimeStamp") + @Mapping(target = "commodity", source = "commodity") + @Mapping(target = "tariffProfile", source = "tariffProfile") + @Mapping(target = "readCycle", source = "readCycle") + @Mapping(target = "tariffRiderRefs", source = "tariffRiderRefs", qualifiedByName = "entityListToTariffRiderRefsDto") + @Mapping(target = "billingChargeSource", source = "billingChargeSource") UsageSummaryDto toDto(UsageSummaryEntity entity); /** * Converts a UsageSummaryDto to a UsageSummaryEntity. - * Maps usage summary data including billing period and cost information. - * + * Maps usage summary data including billing period, cost information, consumption summaries, + * and tariff details per ESPI 4.0 specification. + * * @param dto the usage summary DTO * @return the usage summary entity */ @@ -80,9 +103,26 @@ public interface UsageSummaryMapper { @Mapping(target = "billLastPeriod", source = "billLastPeriod") @Mapping(target = "billToDate", source = "billToDate") @Mapping(target = "costAdditionalLastPeriod", source = "costAdditionalLastPeriod") + @Mapping(target = "costAdditionalDetailLastPeriod", source = "costAdditionalDetailLastPeriod") @Mapping(target = "currency", source = "currency") + @Mapping(target = "overallConsumptionLastPeriod", source = "overallConsumptionLastPeriod") + @Mapping(target = "currentBillingPeriodOverAllConsumption", source = "currentBillingPeriodOverAllConsumption") + @Mapping(target = "currentDayLastYearNetConsumption", source = "currentDayLastYearNetConsumption") + @Mapping(target = "currentDayNetConsumption", source = "currentDayNetConsumption") + @Mapping(target = "currentDayOverallConsumption", source = "currentDayOverallConsumption") + @Mapping(target = "peakDemand", source = "peakDemand") + @Mapping(target = "previousDayLastYearOverallConsumption", source = "previousDayLastYearOverallConsumption") + @Mapping(target = "previousDayNetConsumption", source = "previousDayNetConsumption") + @Mapping(target = "previousDayOverallConsumption", source = "previousDayOverallConsumption") @Mapping(target = "qualityOfReading", source = "qualityOfReading") - @Mapping(target = "statusTimeStamp", source = "statusTimeStamp", qualifiedByName = "offsetToLong") + @Mapping(target = "ratchetDemand", source = "ratchetDemand") + @Mapping(target = "ratchetDemandPeriod", source = "ratchetDemandPeriod") + @Mapping(target = "statusTimeStamp", source = "statusTimeStamp") + @Mapping(target = "commodity", source = "commodity") + @Mapping(target = "tariffProfile", source = "tariffProfile") + @Mapping(target = "readCycle", source = "readCycle") + @Mapping(target = "tariffRiderRefs", source = "tariffRiderRefs", qualifiedByName = "tariffRiderRefsDtoToEntityList") + @Mapping(target = "billingChargeSource", source = "billingChargeSource") @Mapping(target = "usagePoint", ignore = true) // Relationship handled separately @Mapping(target = "created", ignore = true) // Inherited from IdentifiedObject @Mapping(target = "relatedLinks", ignore = true) @@ -93,14 +133,39 @@ public interface UsageSummaryMapper { /** * Updates an existing UsageSummaryEntity with data from a UsageSummaryDto. * Useful for merge operations where the entity ID should be preserved. - * + * Maps all ESPI 4.0 fields while preserving entity identity and relationships. + * * @param dto the source DTO * @param entity the target entity to update */ @Mapping(target = "id", ignore = true) @Mapping(target = "published", source = "published", qualifiedByName = "offsetToLocal") @Mapping(target = "updated", source = "updated", qualifiedByName = "offsetToLocal") - @Mapping(target = "statusTimeStamp", source = "statusTimeStamp", qualifiedByName = "offsetToLong") + @Mapping(target = "description", source = "description") + @Mapping(target = "billingPeriod", source = "billingPeriod") + @Mapping(target = "billLastPeriod", source = "billLastPeriod") + @Mapping(target = "billToDate", source = "billToDate") + @Mapping(target = "costAdditionalLastPeriod", source = "costAdditionalLastPeriod") + @Mapping(target = "costAdditionalDetailLastPeriod", source = "costAdditionalDetailLastPeriod") + @Mapping(target = "currency", source = "currency") + @Mapping(target = "overallConsumptionLastPeriod", source = "overallConsumptionLastPeriod") + @Mapping(target = "currentBillingPeriodOverAllConsumption", source = "currentBillingPeriodOverAllConsumption") + @Mapping(target = "currentDayLastYearNetConsumption", source = "currentDayLastYearNetConsumption") + @Mapping(target = "currentDayNetConsumption", source = "currentDayNetConsumption") + @Mapping(target = "currentDayOverallConsumption", source = "currentDayOverallConsumption") + @Mapping(target = "peakDemand", source = "peakDemand") + @Mapping(target = "previousDayLastYearOverallConsumption", source = "previousDayLastYearOverallConsumption") + @Mapping(target = "previousDayNetConsumption", source = "previousDayNetConsumption") + @Mapping(target = "previousDayOverallConsumption", source = "previousDayOverallConsumption") + @Mapping(target = "qualityOfReading", source = "qualityOfReading") + @Mapping(target = "ratchetDemand", source = "ratchetDemand") + @Mapping(target = "ratchetDemandPeriod", source = "ratchetDemandPeriod") + @Mapping(target = "statusTimeStamp", source = "statusTimeStamp") + @Mapping(target = "commodity", source = "commodity") + @Mapping(target = "tariffProfile", source = "tariffProfile") + @Mapping(target = "readCycle", source = "readCycle") + @Mapping(target = "tariffRiderRefs", source = "tariffRiderRefs", qualifiedByName = "tariffRiderRefsDtoToEntityList") + @Mapping(target = "billingChargeSource", source = "billingChargeSource") @Mapping(target = "usagePoint", ignore = true) // Relationship handled separately @Mapping(target = "created", ignore = true) // Inherited from IdentifiedObject @Mapping(target = "relatedLinks", ignore = true) diff --git a/openespi-common/src/main/resources/db/vendor/h2/V2__H2_Specific_Tables.sql b/openespi-common/src/main/resources/db/vendor/h2/V2__H2_Specific_Tables.sql index bd334b0b..a57caec2 100644 --- a/openespi-common/src/main/resources/db/vendor/h2/V2__H2_Specific_Tables.sql +++ b/openespi-common/src/main/resources/db/vendor/h2/V2__H2_Specific_Tables.sql @@ -449,6 +449,14 @@ CREATE TABLE usage_summaries ratchet_demand_value BIGINT, ratchet_demand_reading_type_ref VARCHAR(512), + -- ESPI 4.0 extension fields (Phase 11) + commodity INTEGER, + tariff_profile VARCHAR(256), + read_cycle VARCHAR(256), + + -- Embedded BillingChargeSource: billingChargeSource + billing_charge_source_agency_name VARCHAR(256), + -- Foreign key relationships usage_point_id CHAR(36), @@ -460,6 +468,9 @@ CREATE INDEX idx_usage_summary_usage_point_id ON usage_summaries (usage_point_id CREATE INDEX idx_usage_summary_billing_period_start ON usage_summaries (billing_period_start); CREATE INDEX idx_usage_summary_created ON usage_summaries (created); CREATE INDEX idx_usage_summary_updated ON usage_summaries (updated); +CREATE INDEX idx_usage_summary_commodity ON usage_summaries (commodity); +CREATE INDEX idx_usage_summary_tariff_profile ON usage_summaries (tariff_profile); +CREATE INDEX idx_usage_summary_read_cycle ON usage_summaries (read_cycle); -- Related Links Table for Usage Summaries CREATE TABLE usage_summary_related_links @@ -472,6 +483,30 @@ CREATE TABLE usage_summary_related_links -- Create index for usage_summary_related_links table CREATE INDEX idx_usage_summary_related_links ON usage_summary_related_links (usage_summary_id); +-- Tariff Rider Ref Table (Object-based entity, no IdentifiedObject) +-- TariffRiderRef extends Object per ESPI 4.0 XSD (espi.xsd:1602-1627) +-- XSD sequence: riderType → enrollmentStatus → effectiveDate +-- Part of TariffRiderRefs collection in UsageSummary (Phase 11) +CREATE TABLE tariff_rider_refs +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + + -- ESPI 4.0 fields in XSD sequence order + rider_type VARCHAR(256) NOT NULL, + enrollment_status VARCHAR(32) NOT NULL, + effective_date BIGINT NOT NULL, + + -- Foreign key relationship (parent: UsageSummary) + usage_summary_id CHAR(36), + + FOREIGN KEY (usage_summary_id) REFERENCES usage_summaries (id) ON DELETE CASCADE +); + +-- Create indexes for tariff_rider_refs table +CREATE INDEX idx_tariff_rider_ref_usage_summary ON tariff_rider_refs (usage_summary_id); +CREATE INDEX idx_tariff_rider_ref_rider_type ON tariff_rider_refs (rider_type); +CREATE INDEX idx_tariff_rider_ref_enrollment_status ON tariff_rider_refs (enrollment_status); + -- Line Item Table (Object-based entity, no IdentifiedObject) -- LineItem extends Object per ESPI 4.0 XSD (espi.xsd:1449) -- XSD sequence: amount → rounding → dateTime → note → measurement → itemKind → unitCost → itemPeriod diff --git a/openespi-common/src/main/resources/db/vendor/mysql/V2__MySQL_Specific_Tables.sql b/openespi-common/src/main/resources/db/vendor/mysql/V2__MySQL_Specific_Tables.sql index f65dbf1d..2ec9948e 100644 --- a/openespi-common/src/main/resources/db/vendor/mysql/V2__MySQL_Specific_Tables.sql +++ b/openespi-common/src/main/resources/db/vendor/mysql/V2__MySQL_Specific_Tables.sql @@ -452,6 +452,14 @@ CREATE TABLE usage_summaries ratchet_demand_value BIGINT, ratchet_demand_reading_type_ref VARCHAR(512), + -- ESPI 4.0 extension fields (Phase 11) + commodity INTEGER, + tariff_profile VARCHAR(256), + read_cycle VARCHAR(256), + + -- Embedded BillingChargeSource: billingChargeSource + billing_charge_source_agency_name VARCHAR(256), + -- Foreign key relationships usage_point_id CHAR(36), @@ -460,7 +468,10 @@ CREATE TABLE usage_summaries INDEX idx_usage_summary_usage_point_id (usage_point_id), INDEX idx_usage_summary_billing_period_start (billing_period_start), INDEX idx_usage_summary_created (created), - INDEX idx_usage_summary_updated (updated) + INDEX idx_usage_summary_updated (updated), + INDEX idx_usage_summary_commodity (commodity), + INDEX idx_usage_summary_tariff_profile (tariff_profile), + INDEX idx_usage_summary_read_cycle (read_cycle) ); -- Related Links Table for Usage Summaries @@ -472,6 +483,29 @@ CREATE TABLE usage_summary_related_links INDEX idx_usage_summary_related_links (usage_summary_id) ); +-- Tariff Rider Ref Table (Object-based entity, no IdentifiedObject) +-- TariffRiderRef extends Object per ESPI 4.0 XSD (espi.xsd:1602-1627) +-- XSD sequence: riderType → enrollmentStatus → effectiveDate +-- Part of TariffRiderRefs collection in UsageSummary (Phase 11) +CREATE TABLE tariff_rider_refs +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + + -- ESPI 4.0 fields in XSD sequence order + rider_type VARCHAR(256) NOT NULL, + enrollment_status VARCHAR(32) NOT NULL, + effective_date BIGINT NOT NULL, + + -- Foreign key relationship (parent: UsageSummary) + usage_summary_id CHAR(36), + + FOREIGN KEY (usage_summary_id) REFERENCES usage_summaries (id) ON DELETE CASCADE, + + INDEX idx_tariff_rider_ref_usage_summary (usage_summary_id), + INDEX idx_tariff_rider_ref_rider_type (rider_type), + INDEX idx_tariff_rider_ref_enrollment_status (enrollment_status) +); + -- Line Item Table (Object-based entity, no IdentifiedObject) -- LineItem extends Object per ESPI 4.0 XSD (espi.xsd:1449) -- XSD sequence: amount → rounding → dateTime → note → measurement → itemKind → unitCost → itemPeriod diff --git a/openespi-common/src/main/resources/db/vendor/postgres/V2__PostgreSQL_Specific_Tables.sql b/openespi-common/src/main/resources/db/vendor/postgres/V2__PostgreSQL_Specific_Tables.sql index aaf64fe2..3914b5a5 100644 --- a/openespi-common/src/main/resources/db/vendor/postgres/V2__PostgreSQL_Specific_Tables.sql +++ b/openespi-common/src/main/resources/db/vendor/postgres/V2__PostgreSQL_Specific_Tables.sql @@ -440,6 +440,14 @@ CREATE TABLE usage_summaries ratchet_demand_value BIGINT, ratchet_demand_reading_type_ref VARCHAR(512), + -- ESPI 4.0 extension fields (Phase 11) + commodity INTEGER, + tariff_profile VARCHAR(256), + read_cycle VARCHAR(256), + + -- Embedded BillingChargeSource: billingChargeSource + billing_charge_source_agency_name VARCHAR(256), + -- Foreign key relationships usage_point_id CHAR(36), @@ -450,6 +458,9 @@ CREATE INDEX idx_usage_summary_usage_point_id ON usage_summaries (usage_point_id CREATE INDEX idx_usage_summary_billing_period_start ON usage_summaries (billing_period_start); CREATE INDEX idx_usage_summary_created ON usage_summaries (created); CREATE INDEX idx_usage_summary_updated ON usage_summaries (updated); +CREATE INDEX idx_usage_summary_commodity ON usage_summaries (commodity); +CREATE INDEX idx_usage_summary_tariff_profile ON usage_summaries (tariff_profile); +CREATE INDEX idx_usage_summary_read_cycle ON usage_summaries (read_cycle); -- Related Links Table for Usage Summaries CREATE TABLE usage_summary_related_links @@ -461,6 +472,29 @@ CREATE TABLE usage_summary_related_links CREATE INDEX idx_usage_summary_related_links ON usage_summary_related_links (usage_summary_id); +-- Tariff Rider Ref Table (Object-based entity, no IdentifiedObject) +-- TariffRiderRef extends Object per ESPI 4.0 XSD (espi.xsd:1602-1627) +-- XSD sequence: riderType → enrollmentStatus → effectiveDate +-- Part of TariffRiderRefs collection in UsageSummary (Phase 11) +CREATE TABLE tariff_rider_refs +( + id BIGSERIAL PRIMARY KEY, + + -- ESPI 4.0 fields in XSD sequence order + rider_type VARCHAR(256) NOT NULL, + enrollment_status VARCHAR(32) NOT NULL, + effective_date BIGINT NOT NULL, + + -- Foreign key relationship (parent: UsageSummary) + usage_summary_id CHAR(36), + + FOREIGN KEY (usage_summary_id) REFERENCES usage_summaries (id) ON DELETE CASCADE +); + +CREATE INDEX idx_tariff_rider_ref_usage_summary ON tariff_rider_refs (usage_summary_id); +CREATE INDEX idx_tariff_rider_ref_rider_type ON tariff_rider_refs (rider_type); +CREATE INDEX idx_tariff_rider_ref_enrollment_status ON tariff_rider_refs (enrollment_status); + -- Line Item Table (Object-based entity, no IdentifiedObject) -- LineItem extends Object per ESPI 4.0 XSD (espi.xsd:1449) -- XSD sequence: amount → rounding → dateTime → note → measurement → itemKind → unitCost → itemPeriod diff --git a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/TestApplication.java b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/TestApplication.java index 512778aa..a87f3e00 100644 --- a/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/TestApplication.java +++ b/openespi-common/src/test/java/org/greenbuttonalliance/espi/common/TestApplication.java @@ -35,7 +35,8 @@ @Profile({"test", "test-mysql", "test-postgres"}) @EntityScan(basePackages = { "org.greenbuttonalliance.espi.common.domain.usage", - "org.greenbuttonalliance.espi.common.domain.customer" + "org.greenbuttonalliance.espi.common.domain.customer", + "org.greenbuttonalliance.espi.common.domain.common" }) @EnableJpaRepositories(basePackages = "org.greenbuttonalliance.espi.common.repositories") public class TestApplication { diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/DataCustodianApplication.java b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/DataCustodianApplication.java index bbeeef84..df8248cd 100644 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/DataCustodianApplication.java +++ b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/DataCustodianApplication.java @@ -45,7 +45,8 @@ }) @EntityScan(basePackages = { "org.greenbuttonalliance.espi.common.domain.usage", - "org.greenbuttonalliance.espi.common.domain.customer" + "org.greenbuttonalliance.espi.common.domain.customer", + "org.greenbuttonalliance.espi.common.domain.common" }) @EnableJpaRepositories(basePackages = { "org.greenbuttonalliance.espi.common.repositories"