33import de .rwth .idsg .steve .SteveException ;
44import de .rwth .idsg .steve .repository .TransactionRepository ;
55import de .rwth .idsg .steve .repository .dto .Transaction ;
6+ import de .rwth .idsg .steve .repository .dto .TransactionDetails ;
67import de .rwth .idsg .steve .utils .CustomDSL ;
78import de .rwth .idsg .steve .utils .DateTimeUtils ;
89import de .rwth .idsg .steve .web .dto .TransactionQueryForm ;
10+ import jooq .steve .db .tables .records .ConnectorMeterValueRecord ;
911import org .joda .time .DateTime ;
12+ import org .jooq .Condition ;
1013import org .jooq .DSLContext ;
1114import org .jooq .Record10 ;
1215import org .jooq .Record8 ;
16+ import org .jooq .RecordMapper ;
1317import org .jooq .SelectQuery ;
18+ import org .jooq .Table ;
19+ import org .jooq .impl .DSL ;
1420import org .springframework .beans .factory .annotation .Autowired ;
1521import org .springframework .stereotype .Repository ;
1622
2026import static de .rwth .idsg .steve .utils .CustomDSL .date ;
2127import static jooq .steve .db .tables .ChargeBox .CHARGE_BOX ;
2228import static jooq .steve .db .tables .Connector .CONNECTOR ;
29+ import static jooq .steve .db .tables .ConnectorMeterValue .CONNECTOR_METER_VALUE ;
2330import static jooq .steve .db .tables .OcppTag .OCPP_TAG ;
2431import static jooq .steve .db .tables .Transaction .TRANSACTION ;
2532
@@ -36,18 +43,7 @@ public class TransactionRepositoryImpl implements TransactionRepository {
3643 @ SuppressWarnings ("unchecked" )
3744 public List <Transaction > getTransactions (TransactionQueryForm form ) {
3845 return getInternal (form ).fetch ()
39- .map (r -> Transaction .builder ()
40- .id (r .value1 ())
41- .chargeBoxId (r .value2 ())
42- .connectorId (r .value3 ())
43- .ocppIdTag (r .value4 ())
44- .startTimestamp (DateTimeUtils .humanize (r .value5 ()))
45- .startValue (r .value6 ())
46- .stopTimestamp (DateTimeUtils .humanize (r .value7 ()))
47- .stopValue (r .value8 ())
48- .chargeBoxPk (r .value9 ())
49- .ocppTagPk (r .value10 ())
50- .build ());
46+ .map (new TransactionMapper ());
5147 }
5248
5349 @ Override
@@ -68,6 +64,105 @@ public List<Integer> getActiveTransactionIds(String chargeBoxId) {
6864 .fetch (TRANSACTION .TRANSACTION_PK );
6965 }
7066
67+ @ Override
68+ public TransactionDetails getDetails (int transactionPk ) {
69+
70+ // -------------------------------------------------------------------------
71+ // Step 1: Collect general data about transaction
72+ // -------------------------------------------------------------------------
73+
74+ TransactionQueryForm form = new TransactionQueryForm ();
75+ form .setTransactionPk (transactionPk );
76+ form .setType (TransactionQueryForm .QueryType .ALL );
77+ form .setPeriodType (TransactionQueryForm .QueryPeriodType .ALL );
78+
79+ Record10 <Integer , String , Integer , String , DateTime , String , DateTime , String , Integer , Integer >
80+ transaction = getInternal (form ).fetchOne ();
81+
82+ DateTime startTimestamp = transaction .value5 ();
83+ DateTime stopTimestamp = transaction .value7 ();
84+ String stopValue = transaction .value8 ();
85+ String chargeBoxId = transaction .value2 ();
86+ int connectorId = transaction .value3 ();
87+
88+ // -------------------------------------------------------------------------
89+ // Step 2: Collect intermediate meter values
90+ // -------------------------------------------------------------------------
91+
92+ Condition timestampCondition ;
93+ if (stopTimestamp == null && stopValue == null ) {
94+ // active transaction
95+ timestampCondition = CONNECTOR_METER_VALUE .VALUE_TIMESTAMP .greaterOrEqual (startTimestamp );
96+ } else {
97+ // finished transaction
98+ timestampCondition = CONNECTOR_METER_VALUE .VALUE_TIMESTAMP .between (startTimestamp , stopTimestamp );
99+ }
100+
101+ // Case 1: Ideal and most accurate case. Station sends meter values with transaction id set.
102+ //
103+ SelectQuery <ConnectorMeterValueRecord > transactionQuery =
104+ ctx .selectFrom (CONNECTOR_METER_VALUE )
105+ .where (CONNECTOR_METER_VALUE .TRANSACTION_PK .eq (transactionPk ))
106+ .getQuery ();
107+
108+ // Case 2: Fall back to filtering according to time windows
109+ //
110+ SelectQuery <ConnectorMeterValueRecord > timestampQuery =
111+ ctx .selectFrom (CONNECTOR_METER_VALUE )
112+ .where (CONNECTOR_METER_VALUE .CONNECTOR_PK .eq (ctx .select (CONNECTOR .CONNECTOR_PK )
113+ .from (CONNECTOR )
114+ .where (CONNECTOR .CHARGE_BOX_ID .eq (chargeBoxId ))
115+ .and (CONNECTOR .CONNECTOR_ID .eq (connectorId ))))
116+ .and (timestampCondition )
117+ .getQuery ();
118+
119+ // Actually, either case 1 applies or 2. If we retrieved values using 1, case 2 is should not be
120+ // executed (best case). In worst case (1 returns empty list and we fall back to case 2) though,
121+ // we make two db calls. Alternatively, we can pass both queries in one go, and make the db work.
122+ //
123+ // UNION removes all duplicate records
124+ //
125+ Table <ConnectorMeterValueRecord > t1 = transactionQuery .union (timestampQuery ).asTable ("t1" );
126+
127+ // -------------------------------------------------------------------------
128+ // Step 3: Charging station might send meter vales at fixed intervals (e.g.
129+ // every 15 min) regardless of the fact that connector's meter value did not
130+ // change (e.g. vehicle is fully charged, but cable is still connected). This
131+ // yields multiple entries in db with the same value but different timestamp.
132+ // We are only interested in the first arriving entry.
133+ // -------------------------------------------------------------------------
134+
135+ List <TransactionDetails .MeterValues > values =
136+ ctx .select (
137+ DSL .min (t1 .field (2 , DateTime .class )),
138+ t1 .field (3 , String .class ),
139+ t1 .field (4 , String .class ),
140+ t1 .field (5 , String .class ),
141+ t1 .field (6 , String .class ),
142+ t1 .field (7 , String .class ),
143+ t1 .field (8 , String .class ))
144+ .from (t1 )
145+ .groupBy (
146+ t1 .field (3 ),
147+ t1 .field (4 ),
148+ t1 .field (5 ),
149+ t1 .field (6 ),
150+ t1 .field (7 ),
151+ t1 .field (8 ))
152+ .fetch ()
153+ .map (r -> TransactionDetails .MeterValues .builder ()
154+ .valueTimestamp (r .value1 ())
155+ .value (r .value2 ())
156+ .readingContext (r .value3 ())
157+ .format (r .value4 ())
158+ .measurand (r .value5 ())
159+ .location (r .value6 ())
160+ .unit (r .value7 ())
161+ .build ());
162+
163+ return new TransactionDetails (new TransactionMapper ().map (transaction ), values );
164+ }
165+
71166 // -------------------------------------------------------------------------
72167 // Private helpers
73168 // -------------------------------------------------------------------------
@@ -183,4 +278,25 @@ private void processType(SelectQuery selectQuery, TransactionQueryForm form) {
183278 throw new SteveException ("Unknown enum type" );
184279 }
185280 }
281+
282+ private static class TransactionMapper
283+ implements RecordMapper <Record10 <Integer , String , Integer , String , DateTime , String , DateTime ,
284+ String , Integer , Integer >, Transaction > {
285+ @ Override
286+ public Transaction map (Record10 <Integer , String , Integer , String , DateTime , String , DateTime ,
287+ String , Integer , Integer > r ) {
288+ return Transaction .builder ()
289+ .id (r .value1 ())
290+ .chargeBoxId (r .value2 ())
291+ .connectorId (r .value3 ())
292+ .ocppIdTag (r .value4 ())
293+ .startTimestamp (DateTimeUtils .humanize (r .value5 ()))
294+ .startValue (r .value6 ())
295+ .stopTimestamp (DateTimeUtils .humanize (r .value7 ()))
296+ .stopValue (r .value8 ())
297+ .chargeBoxPk (r .value9 ())
298+ .ocppTagPk (r .value10 ())
299+ .build ();
300+ }
301+ }
186302}
0 commit comments