Skip to content

Commit 3a56398

Browse files
committed
Implemented endpoint to get consumption for a supply given a range of dates
1 parent 6f8ec5c commit 3a56398

File tree

13 files changed

+847
-17
lines changed

13 files changed

+847
-17
lines changed

src/main/java/org/lucoenergia/conluz/domain/consumption/datadis/get/GetDatadisConsumptionRepository.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,21 @@
55
import org.lucoenergia.conluz.domain.consumption.datadis.DatadisConsumption;
66

77
import java.time.Month;
8+
import java.time.OffsetDateTime;
89
import java.util.List;
910

1011
public interface GetDatadisConsumptionRepository {
1112

1213
List<DatadisConsumption> getHourlyConsumptionsByMonth(@NotNull Supply supply, @NotNull Month month, @NotNull int year);
14+
15+
/**
16+
* Retrieves a list of daily consumption data within a specified date range for the given supply.
17+
*
18+
* @param supply the supply for which the consumption data is retrieved, must not be null
19+
* @param startDate the start date of the range, inclusive, must not be null
20+
* @param endDate the end date of the range, inclusive, must not be null
21+
* @return a list of {@code DatadisConsumption} objects representing daily consumption data within the specified date range
22+
*/
23+
List<DatadisConsumption> getDailyConsumptionsByRangeOfDates(@NotNull Supply supply,
24+
@NotNull OffsetDateTime startDate, @NotNull OffsetDateTime endDate);
1325
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.lucoenergia.conluz.domain.consumption.datadis.get;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
import org.lucoenergia.conluz.domain.consumption.datadis.DatadisConsumption;
5+
import org.lucoenergia.conluz.domain.shared.SupplyId;
6+
7+
import java.time.OffsetDateTime;
8+
import java.util.List;
9+
10+
/**
11+
* Service for retrieving Datadis consumption data.
12+
*/
13+
public interface GetDatadisConsumptionService {
14+
15+
/**
16+
* Retrieves daily consumption data for a specific supply within a date range.
17+
* Access is restricted based on user role and ownership.
18+
*
19+
* @param supplyId the supply ID for which to retrieve consumption data
20+
* @param startDate the start date of the range, inclusive
21+
* @param endDate the end date of the range, inclusive
22+
* @return a list of daily consumption data
23+
*/
24+
List<DatadisConsumption> getDailyConsumptionBySupply(@NotNull SupplyId supplyId,
25+
@NotNull OffsetDateTime startDate,
26+
@NotNull OffsetDateTime endDate);
27+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package org.lucoenergia.conluz.infrastructure.admin.supply.consumption;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
5+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
6+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
7+
import org.lucoenergia.conluz.domain.consumption.datadis.DatadisConsumption;
8+
import org.lucoenergia.conluz.domain.consumption.datadis.get.GetDatadisConsumptionService;
9+
import org.lucoenergia.conluz.domain.shared.SupplyId;
10+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.ApiTag;
11+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.BadRequestErrorResponse;
12+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.ForbiddenErrorResponse;
13+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.InternalServerErrorResponse;
14+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.NotFoundErrorResponse;
15+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.UnauthorizedErrorResponse;
16+
import org.springframework.format.annotation.DateTimeFormat;
17+
import org.springframework.security.access.prepost.PreAuthorize;
18+
import org.springframework.web.bind.annotation.GetMapping;
19+
import org.springframework.web.bind.annotation.PathVariable;
20+
import org.springframework.web.bind.annotation.RequestMapping;
21+
import org.springframework.web.bind.annotation.RequestParam;
22+
import org.springframework.web.bind.annotation.RestController;
23+
24+
import java.time.OffsetDateTime;
25+
import java.util.List;
26+
import java.util.UUID;
27+
28+
/**
29+
* Controller for retrieving daily consumption data for a specific supply
30+
*/
31+
@RestController
32+
@RequestMapping("/api/v1/supplies/{id}/consumption/daily")
33+
public class GetSupplyDailyConsumptionController {
34+
35+
private final GetDatadisConsumptionService getDatadisConsumptionService;
36+
37+
public GetSupplyDailyConsumptionController(GetDatadisConsumptionService getDatadisConsumptionService) {
38+
this.getDatadisConsumptionService = getDatadisConsumptionService;
39+
}
40+
41+
@GetMapping
42+
@Operation(
43+
summary = "Retrieves daily consumption data for a specific supply",
44+
description = """
45+
This endpoint retrieves daily consumption data from Datadis for a specific supply within a given date range.
46+
47+
**Authorization Rules:**
48+
- Users with role ADMIN can retrieve consumption data for any supply
49+
- Users with role PARTNER can only retrieve consumption data for their own supplies
50+
51+
The consumption data includes:
52+
- Total consumption in kWh
53+
- Surplus energy (energy sent to grid)
54+
- Self-consumption energy
55+
- Obtain method (Real/Estimated)
56+
57+
Data is aggregated by day within the specified date range.
58+
""",
59+
tags = ApiTag.SUPPLIES,
60+
operationId = "getSupplyDailyConsumption",
61+
security = @SecurityRequirement(name = "bearerToken")
62+
)
63+
@ApiResponses(value = {
64+
@ApiResponse(
65+
responseCode = "200",
66+
description = "Consumption data retrieved successfully",
67+
useReturnTypeSchema = true
68+
)
69+
})
70+
@BadRequestErrorResponse
71+
@InternalServerErrorResponse
72+
@UnauthorizedErrorResponse
73+
@ForbiddenErrorResponse
74+
@NotFoundErrorResponse
75+
@PreAuthorize("isAuthenticated()")
76+
public List<DatadisConsumption> getSupplyDailyConsumption(
77+
@PathVariable("id") UUID id,
78+
@RequestParam("startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime startDate,
79+
@RequestParam("endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime endDate) {
80+
81+
return getDatadisConsumptionService.getDailyConsumptionBySupply(SupplyId.of(id), startDate, endDate);
82+
}
83+
}

src/main/java/org/lucoenergia/conluz/infrastructure/consumption/datadis/DatadisConsumptionPoint.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public class DatadisConsumptionPoint {
1919
private String obtainMethod;
2020
@Column(name = "surplus_energy_kwh")
2121
private Double surplusEnergyKWh;
22+
@Column(name = "self_consumption_energy_kwh")
23+
private Double selfConsumptionEnergyKWh;
2224

2325
public Instant getTime() {
2426
return time;
@@ -39,4 +41,8 @@ public String getObtainMethod() {
3941
public Double getSurplusEnergyKWh() {
4042
return surplusEnergyKWh;
4143
}
44+
45+
public Double getSelfConsumptionEnergyKWh() {
46+
return selfConsumptionEnergyKWh;
47+
}
4248
}

src/main/java/org/lucoenergia/conluz/infrastructure/consumption/datadis/get/GetDatadisConsumptionRepositoryInflux.java

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
import org.lucoenergia.conluz.domain.consumption.datadis.DatadisConsumption;
99
import org.lucoenergia.conluz.domain.consumption.datadis.get.GetDatadisConsumptionRepository;
1010
import org.lucoenergia.conluz.infrastructure.consumption.datadis.DatadisConsumptionPoint;
11-
import org.lucoenergia.conluz.infrastructure.consumption.datadis.DatadisDateTimeConverter;
1211
import org.lucoenergia.conluz.infrastructure.consumption.datadis.config.DatadisConfigEntity;
1312
import org.lucoenergia.conluz.infrastructure.shared.db.influxdb.InfluxDbConnectionManager;
13+
import org.lucoenergia.conluz.infrastructure.shared.db.influxdb.InfluxDuration;
1414
import org.lucoenergia.conluz.infrastructure.shared.time.DateConverter;
1515
import org.springframework.beans.factory.annotation.Qualifier;
1616
import org.springframework.stereotype.Repository;
1717

1818
import java.time.Month;
19+
import java.time.OffsetDateTime;
1920
import java.util.List;
2021

2122
@Repository
@@ -24,13 +25,11 @@ public class GetDatadisConsumptionRepositoryInflux implements GetDatadisConsumpt
2425

2526
private final InfluxDbConnectionManager influxDbConnectionManager;
2627
private final DateConverter dateConverter;
27-
private final DatadisDateTimeConverter datadisDateTimeConverter;
2828

2929
public GetDatadisConsumptionRepositoryInflux(InfluxDbConnectionManager influxDbConnectionManager,
30-
DateConverter dateConverter, DatadisDateTimeConverter datadisDateTimeConverter) {
30+
DateConverter dateConverter) {
3131
this.influxDbConnectionManager = influxDbConnectionManager;
3232
this.dateConverter = dateConverter;
33-
this.datadisDateTimeConverter = datadisDateTimeConverter;
3433
}
3534

3635
@Override
@@ -56,19 +55,62 @@ public List<DatadisConsumption> getHourlyConsumptionsByMonth(Supply supply, Mont
5655
}
5756
}
5857

58+
@Override
59+
public List<DatadisConsumption> getDailyConsumptionsByRangeOfDates(Supply supply, OffsetDateTime startDate, OffsetDateTime endDate) {
60+
try (InfluxDB connection = influxDbConnectionManager.getConnection()) {
61+
62+
Query query = new Query(String.format(
63+
"""
64+
SELECT
65+
SUM("consumption_kwh") AS "consumption_kwh",
66+
SUM("surplus_energy_kwh") AS "surplus_energy_kwh",
67+
SUM("self_consumption_energy_kwh") AS "self_consumption_energy_kwh",
68+
LAST("obtain_method") AS "obtain_method"
69+
FROM "%s"
70+
WHERE cups = '%s'
71+
AND time >= '%s'
72+
AND time <= '%s'
73+
GROUP BY time(%s), cups
74+
""",
75+
DatadisConfigEntity.CONSUMPTION_KWH_MEASUREMENT,
76+
supply.getCode(),
77+
dateConverter.convertToString(startDate),
78+
dateConverter.convertToString(endDate),
79+
InfluxDuration.DAILY));
80+
81+
QueryResult queryResult = connection.query(query);
82+
83+
InfluxDBResultMapper resultMapper = new InfluxDBResultMapper();
84+
List<DatadisConsumptionPoint> consumptionPoints = resultMapper.toPOJO(queryResult, DatadisConsumptionPoint.class);
85+
return mapToConsumption(consumptionPoints);
86+
}
87+
}
88+
5989
private List<DatadisConsumption> mapToConsumption(List<DatadisConsumptionPoint> consumptionPoints) {
6090
// Map fields from datadisConsumptionPoint to consumption here
6191
return consumptionPoints.stream()
6292
.map(consumptionPoint -> {
6393
DatadisConsumption consumption = new DatadisConsumption();
6494
consumption.setCups(consumptionPoint.getCups());
65-
consumption.setDate(datadisDateTimeConverter.convertFromInstantToDate(consumptionPoint.getTime()));
66-
consumption.setTime(datadisDateTimeConverter.convertFromInstantToTime(consumptionPoint.getTime()));
67-
consumption.setConsumptionKWh(consumptionPoint.getConsumptionKWh().floatValue());
95+
consumption.setDate(dateConverter.convertFromInstantToStringDate(consumptionPoint.getTime()));
96+
consumption.setTime(dateConverter.convertFromInstantToStringTime(consumptionPoint.getTime()));
97+
consumption.setConsumptionKWh(parseToFloat(consumptionPoint.getConsumptionKWh()));
98+
consumption.setSelfConsumptionEnergyKWh(parseToFloat(consumptionPoint.getSelfConsumptionEnergyKWh()));
99+
consumption.setSurplusEnergyKWh(parseToFloat(consumptionPoint.getSurplusEnergyKWh()));
68100
consumption.setObtainMethod(consumptionPoint.getObtainMethod());
69-
consumption.setSurplusEnergyKWh(consumptionPoint.getSurplusEnergyKWh().floatValue());
70101
return consumption;
71102
})
72103
.toList();
73104
}
105+
106+
private Float parseToFloat(Double value) {
107+
if (value == null) {
108+
return 0.0f;
109+
}
110+
try {
111+
return value.floatValue();
112+
} catch (NumberFormatException e) {
113+
return 0.0f;
114+
}
115+
}
74116
}

src/main/java/org/lucoenergia/conluz/infrastructure/consumption/datadis/get/GetDatadisConsumptionRepositoryRest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import okhttp3.OkHttpClient;
77
import okhttp3.Request;
88
import okhttp3.Response;
9+
import org.apache.commons.lang3.NotImplementedException;
910
import org.lucoenergia.conluz.domain.admin.supply.Supply;
1011
import org.lucoenergia.conluz.domain.consumption.datadis.DatadisConsumption;
1112
import org.lucoenergia.conluz.domain.consumption.datadis.get.GetDatadisConsumptionRepository;
@@ -27,6 +28,7 @@
2728
import java.io.IOException;
2829
import java.time.Duration;
2930
import java.time.Month;
31+
import java.time.OffsetDateTime;
3032
import java.util.ArrayList;
3133
import java.util.List;
3234

@@ -112,6 +114,11 @@ public List<DatadisConsumption> getHourlyConsumptionsByMonth(@NotNull Supply sup
112114
return result;
113115
}
114116

117+
@Override
118+
public List<DatadisConsumption> getDailyConsumptionsByRangeOfDates(Supply supply, OffsetDateTime startDate, OffsetDateTime endDate) {
119+
throw new NotImplementedException("Not yet implemented.");
120+
}
121+
115122
private void validateSupply(Supply supply) {
116123
if (supply.getDistributorCode() == null || supply.getDistributorCode().isEmpty()) {
117124
throw new DatadisSupplyConfigurationException("Distributor code is mandatory to get monthly consumption.");
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.lucoenergia.conluz.infrastructure.consumption.datadis.get;
2+
3+
import org.lucoenergia.conluz.domain.admin.supply.Supply;
4+
import org.lucoenergia.conluz.domain.admin.supply.SupplyNotFoundException;
5+
import org.lucoenergia.conluz.domain.admin.supply.get.GetSupplyRepository;
6+
import org.lucoenergia.conluz.domain.admin.user.Role;
7+
import org.lucoenergia.conluz.domain.admin.user.User;
8+
import org.lucoenergia.conluz.domain.admin.user.auth.AuthService;
9+
import org.lucoenergia.conluz.domain.consumption.datadis.DatadisConsumption;
10+
import org.lucoenergia.conluz.domain.consumption.datadis.get.GetDatadisConsumptionRepository;
11+
import org.lucoenergia.conluz.domain.consumption.datadis.get.GetDatadisConsumptionService;
12+
import org.lucoenergia.conluz.domain.shared.SupplyId;
13+
import org.springframework.beans.factory.annotation.Qualifier;
14+
import org.springframework.security.access.AccessDeniedException;
15+
import org.springframework.stereotype.Service;
16+
import org.springframework.transaction.annotation.Transactional;
17+
18+
import java.time.OffsetDateTime;
19+
import java.util.List;
20+
import java.util.Optional;
21+
22+
@Service
23+
@Transactional(readOnly = true)
24+
public class GetDatadisConsumptionServiceImpl implements GetDatadisConsumptionService {
25+
26+
private final GetDatadisConsumptionRepository getDatadisConsumptionRepository;
27+
private final GetSupplyRepository getSupplyRepository;
28+
private final AuthService authService;
29+
30+
public GetDatadisConsumptionServiceImpl(
31+
@Qualifier("getDatadisConsumptionRepositoryInflux") GetDatadisConsumptionRepository getDatadisConsumptionRepository,
32+
GetSupplyRepository getSupplyRepository,
33+
AuthService authService) {
34+
this.getDatadisConsumptionRepository = getDatadisConsumptionRepository;
35+
this.getSupplyRepository = getSupplyRepository;
36+
this.authService = authService;
37+
}
38+
39+
@Override
40+
public List<DatadisConsumption> getDailyConsumptionBySupply(SupplyId supplyId, OffsetDateTime startDate,
41+
OffsetDateTime endDate) {
42+
// Get current authenticated user
43+
User currentUser = authService.getCurrentUser()
44+
.orElseThrow(() -> new IllegalStateException("User must be authenticated"));
45+
46+
// Get the supply
47+
Optional<Supply> supplyOptional = getSupplyRepository.findById(supplyId);
48+
if (supplyOptional.isEmpty()) {
49+
throw new SupplyNotFoundException(supplyId);
50+
}
51+
52+
Supply supply = supplyOptional.get();
53+
54+
// Authorization: ADMIN can access any supply, non-ADMIN can only access their own supplies
55+
boolean isAdmin = currentUser.getRole() == Role.ADMIN;
56+
boolean isOwner = supply.getUser().getId().equals(currentUser.getId());
57+
58+
if (!isAdmin && !isOwner) {
59+
throw new AccessDeniedException("User does not have permission to access this supply's consumption data");
60+
}
61+
62+
// Retrieve consumption data
63+
return getDatadisConsumptionRepository.getDailyConsumptionsByRangeOfDates(supply, startDate, endDate);
64+
}
65+
}

src/main/java/org/lucoenergia/conluz/infrastructure/shared/time/DateConverter.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.lucoenergia.conluz.infrastructure.shared.time;
22

3+
import jakarta.validation.constraints.NotNull;
34
import org.springframework.stereotype.Component;
45

56
import java.time.*;
@@ -8,6 +9,9 @@
89
@Component
910
public class DateConverter {
1011

12+
public static final String DATE_FORMAT = "yyyy/MM/dd";
13+
public static final String TIME_FORMAT = "HH:mm";
14+
1115
private final TimeConfiguration timeConfiguration;
1216

1317
public DateConverter(TimeConfiguration timeConfiguration) {
@@ -23,10 +27,6 @@ public long convertStringDateToMilliseconds(String dateString) {
2327
return dateTime.atZone(zoneId).toInstant().toEpochMilli();
2428
}
2529

26-
public long convertOffsetDateTimeToMilliseconds(OffsetDateTime time) {
27-
return time.toInstant().toEpochMilli();
28-
}
29-
3030
public OffsetDateTime convertInstantToOffsetDateTime(Instant instant) {
3131

3232
ZoneId zoneId = timeConfiguration.getZoneId();
@@ -78,4 +78,22 @@ public String convertToLastDayOfTheMonthAsString(Month month, int year) {
7878
public String convertToFirstDayOfTheMonthAsString(Month month, int year) {
7979
return String.format("%s-%02d-01T00:00:00.000000000Z", year, month.getValue());
8080
}
81+
82+
public String convertFromInstantToStringDate(@NotNull Instant instant) {
83+
84+
ZonedDateTime zonedDateTime = instant.atZone(timeConfiguration.getZoneId());
85+
86+
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
87+
88+
return formatter.format(zonedDateTime);
89+
}
90+
91+
public String convertFromInstantToStringTime(@NotNull Instant instant) {
92+
93+
ZonedDateTime zonedDateTime = instant.atZone(timeConfiguration.getZoneId());
94+
95+
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(TIME_FORMAT);
96+
97+
return formatter.format(zonedDateTime);
98+
}
8199
}

0 commit comments

Comments
 (0)