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"