Skip to content

Commit 1a4b309

Browse files
dfcoffinclaude
andauthored
feat: ESPI 4.0 Schema Compliance - Phase 11: UsageSummary (#77)
* feat: ESPI 4.0 Schema Compliance - Phase 11: UsageSummary Complete rebuild of UsageSummary entity, DTOs, and mappers to achieve full ESPI 4.0 XSD compliance (espi.xsd:806-939). Entity Changes: - Added 5 missing fields to UsageSummaryEntity (commodity, tariffProfile, readCycle, tariffRiderRefs collection, billingChargeSource) - Reordered all 25 fields to match XSD sequence exactly - Added helper methods for tariffRiderRefs collection management New Domain Objects: - TariffRiderRefEntity (Object-based, Long ID with auto-increment) - BillingChargeSource (embeddable value object) DTO Changes: - Completely rebuilt UsageSummaryDto with all 32 fields in XSD order - Added all 10 SummaryMeasurement DTO fields (consumption summaries) - Added ratchetDemandPeriod, costAdditionalDetailLastPeriod collection - Fixed statusTimeStamp type from OffsetDateTime to Long (TimeType) - Created BillingChargeSourceDto for XML marshalling Mapper Changes: - Created TariffRiderRefMapper with collection conversion methods - Updated UsageSummaryMapper with all new field mappings - Added proper @nAmed qualifiers for TariffRiderRefs wrapper conversion Database Migrations: - Added tariff_rider_refs table to V2 migrations (MySQL, PostgreSQL, H2) - Added 4 new columns to usage_summaries table (commodity, tariffProfile, readCycle, billingChargeSource.agencyName) - Added indexes for new columns and foreign key relationships Test Updates: - Updated TestApplication @EntityScan to include domain.common package - All 533 tests passing with Phase 11 changes Related Issues: #28 Co-Authored-By: Claude Sonnet 4.5 <[email protected]> * fix: Add domain.common to DataCustodianApplication entity scan Fixes CI/CD test failures caused by TariffRiderRefEntity not being found in the persistence unit. This entity is located in the domain.common package which was missing from the @EntityScan configuration. The same fix was previously applied to TestApplication.java during Phase 11 development, but DataCustodianApplication.java was overlooked. Resolves ApplicationStartupIntegrationTest and DataCustodianApplicationTest context loading failures in PR #77. Co-Authored-By: Claude Sonnet 4.5 <[email protected]> --------- Co-authored-by: Claude Sonnet 4.5 <[email protected]>
1 parent 8e519f7 commit 1a4b309

File tree

12 files changed

+1071
-115
lines changed

12 files changed

+1071
-115
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
*
3+
* Copyright (c) 2025 Green Button Alliance, Inc.
4+
*
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
*/
19+
20+
package org.greenbuttonalliance.espi.common.domain.common;
21+
22+
import jakarta.persistence.Column;
23+
import jakarta.persistence.Embeddable;
24+
import lombok.AllArgsConstructor;
25+
import lombok.Data;
26+
import lombok.NoArgsConstructor;
27+
28+
import java.io.Serial;
29+
import java.io.Serializable;
30+
31+
/**
32+
* Embeddable value object for BillingChargeSource.
33+
* <p>
34+
* Information about the source of billing charge.
35+
* Per ESPI 4.0 XSD (espi.xsd:1628-1643), BillingChargeSource extends Object
36+
* and contains a single agencyName field.
37+
* <p>
38+
* Embedded within UsageSummary entity.
39+
*/
40+
@Embeddable
41+
@Data
42+
@NoArgsConstructor
43+
@AllArgsConstructor
44+
public class BillingChargeSource implements Serializable {
45+
46+
@Serial
47+
private static final long serialVersionUID = 1L;
48+
49+
/**
50+
* Name of the billing source agency.
51+
* Maximum length 256 characters per String256 type.
52+
*/
53+
@Column(name = "billing_charge_source_agency_name", length = 256)
54+
private String agencyName;
55+
56+
/**
57+
* Checks if this billing charge source has a value.
58+
*
59+
* @return true if agency name is present
60+
*/
61+
public boolean hasValue() {
62+
return agencyName != null && !agencyName.trim().isEmpty();
63+
}
64+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
*
3+
* Copyright (c) 2025 Green Button Alliance, Inc.
4+
*
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
*/
19+
20+
package org.greenbuttonalliance.espi.common.domain.common;
21+
22+
import jakarta.persistence.*;
23+
import jakarta.validation.constraints.NotBlank;
24+
import jakarta.validation.constraints.NotNull;
25+
import lombok.Getter;
26+
import lombok.NoArgsConstructor;
27+
import lombok.Setter;
28+
import org.greenbuttonalliance.espi.common.domain.usage.UsageSummaryEntity;
29+
import org.hibernate.proxy.HibernateProxy;
30+
31+
import java.io.Serial;
32+
import java.io.Serializable;
33+
import java.util.Objects;
34+
35+
/**
36+
* Pure JPA/Hibernate entity for TariffRiderRef without JAXB concerns.
37+
* <p>
38+
* Represents a reference to a tariff rider applied to a usage summary.
39+
* TariffRiderRef extends Object (not IdentifiedObject) per ESPI 4.0 XSD (espi.xsd:1602),
40+
* so it uses Long ID with auto-increment instead of UUID.
41+
* <p>
42+
* Part of TariffRiderRefs collection in UsageSummary.
43+
*/
44+
@Entity
45+
@Table(name = "tariff_rider_refs", indexes = {
46+
@Index(name = "idx_tariff_rider_ref_usage_summary", columnList = "usage_summary_id"),
47+
@Index(name = "idx_tariff_rider_ref_rider_type", columnList = "rider_type"),
48+
@Index(name = "idx_tariff_rider_ref_enrollment_status", columnList = "enrollment_status")
49+
})
50+
@Getter
51+
@Setter
52+
@NoArgsConstructor
53+
public class TariffRiderRefEntity implements Serializable {
54+
55+
@Serial
56+
private static final long serialVersionUID = 1L;
57+
58+
/**
59+
* Primary key - Long ID with auto-increment.
60+
* TariffRiderRef extends Object, not IdentifiedObject.
61+
*/
62+
@Id
63+
@GeneratedValue(strategy = GenerationType.IDENTITY)
64+
@Column(updatable = false, nullable = false)
65+
private Long id;
66+
67+
/**
68+
* Type of tariff rider.
69+
* Rate options applied to the base tariff profile.
70+
* Required field per ESPI 4.0 XSD.
71+
*/
72+
@Column(name = "rider_type", length = 256, nullable = false)
73+
@NotBlank(message = "Rider type cannot be blank")
74+
private String riderType;
75+
76+
/**
77+
* Retail customer's tariff rider enrollment status.
78+
* EnrollmentStatus enumeration values: "unenrolled", "enrolled", "enrolledPending".
79+
* Required field per ESPI 4.0 XSD.
80+
*/
81+
@Column(name = "enrollment_status", length = 32, nullable = false)
82+
@NotBlank(message = "Enrollment status cannot be blank")
83+
private String enrollmentStatus;
84+
85+
/**
86+
* Effective date of retail customer's tariff rider enrollment status.
87+
* Stored as epoch seconds (TimeType in ESPI).
88+
* Required field per ESPI 4.0 XSD.
89+
*/
90+
@Column(name = "effective_date", nullable = false)
91+
@NotNull(message = "Effective date cannot be null")
92+
private Long effectiveDate;
93+
94+
/**
95+
* Parent usage summary that this tariff rider ref belongs to.
96+
* Many tariff rider refs can belong to one usage summary.
97+
*/
98+
@ManyToOne(fetch = FetchType.LAZY)
99+
@JoinColumn(name = "usage_summary_id")
100+
private UsageSummaryEntity usageSummary;
101+
102+
/**
103+
* Constructor with required fields.
104+
*
105+
* @param riderType the type of tariff rider
106+
* @param enrollmentStatus the enrollment status ("unenrolled", "enrolled", "enrolledPending")
107+
* @param effectiveDate the effective date as epoch seconds
108+
*/
109+
public TariffRiderRefEntity(String riderType, String enrollmentStatus, Long effectiveDate) {
110+
this.riderType = riderType;
111+
this.enrollmentStatus = enrollmentStatus;
112+
this.effectiveDate = effectiveDate;
113+
}
114+
115+
/**
116+
* Validates the tariff rider ref has all required fields.
117+
*
118+
* @return true if valid
119+
*/
120+
public boolean isValid() {
121+
return riderType != null && !riderType.trim().isEmpty() &&
122+
enrollmentStatus != null && !enrollmentStatus.trim().isEmpty() &&
123+
effectiveDate != null;
124+
}
125+
126+
@Override
127+
public final boolean equals(Object o) {
128+
if (this == o) return true;
129+
if (o == null) return false;
130+
Class<?> oEffectiveClass = o instanceof HibernateProxy
131+
? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass()
132+
: o.getClass();
133+
Class<?> thisEffectiveClass = this instanceof HibernateProxy
134+
? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass()
135+
: this.getClass();
136+
if (thisEffectiveClass != oEffectiveClass) return false;
137+
TariffRiderRefEntity that = (TariffRiderRefEntity) o;
138+
return getId() != null && Objects.equals(getId(), that.getId());
139+
}
140+
141+
@Override
142+
public final int hashCode() {
143+
return this instanceof HibernateProxy
144+
? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode()
145+
: getClass().hashCode();
146+
}
147+
148+
@Override
149+
public String toString() {
150+
return getClass().getSimpleName() + "(" +
151+
"id = " + id + ", " +
152+
"riderType = " + riderType + ", " +
153+
"enrollmentStatus = " + enrollmentStatus + ", " +
154+
"effectiveDate = " + effectiveDate + ")";
155+
}
156+
}

0 commit comments

Comments
 (0)