1
+ use actix_web:: web:: Data ;
1
2
use chrono:: NaiveDateTime ;
3
+ use diesel:: sql_types:: { BigInt , Text } ;
2
4
use editoast_derive:: ModelV2 ;
5
+ use serde_derive:: { Deserialize , Serialize } ;
6
+ use utoipa:: ToSchema ;
3
7
4
8
#[ derive( Debug , Default , Clone , ModelV2 ) ]
9
+ #[ model( changeset( derive( Deserialize ) ) ) ]
5
10
#[ model( table = crate :: tables:: scenariov2) ]
6
11
pub struct ScenarioV2 {
7
12
pub id : i64 ,
@@ -14,3 +19,232 @@ pub struct ScenarioV2 {
14
19
pub timetable_id : i64 ,
15
20
pub study_id : i64 ,
16
21
}
22
+
23
+ crate :: schemas! {
24
+ ScenarioV2WithCountTrains
25
+ }
26
+
27
+ #[ derive( Debug , Clone , Deserialize , Serialize , QueryableByName , ToSchema ) ]
28
+ pub struct ScenarioWithDetails {
29
+ pub id : i64 ,
30
+ pub infra_id : i64 ,
31
+ pub name : String ,
32
+ pub description : String ,
33
+ pub creation_date : NaiveDateTime ,
34
+ pub last_modification : NaiveDateTime ,
35
+ pub tags : Vec < Option < String > > ,
36
+ pub timetable_id : i64 ,
37
+ pub study_id : i64 ,
38
+ #[ diesel( sql_type = Text ) ]
39
+ pub infra_name : String ,
40
+ #[ diesel( sql_type = Nullable <Text >) ]
41
+ pub electrical_profile_set_name : Option < String > ,
42
+ #[ diesel( sql_type = Array <LightTrainSchedule >) ]
43
+ pub train_schedules : Vec < LightTrainSchedule > ,
44
+ #[ diesel( sql_type = BigInt ) ]
45
+ pub trains_count : i64 ,
46
+ }
47
+
48
+ #[ derive( Debug , Clone , Serialize , QueryableByName , ToSchema ) ]
49
+ pub struct ScenarioV2WithCountTrains {
50
+ pub id : i64 ,
51
+ pub infra_id : i64 ,
52
+ pub name : String ,
53
+ pub description : String ,
54
+ pub creation_date : NaiveDateTime ,
55
+ pub last_modification : NaiveDateTime ,
56
+ pub tags : Vec < Option < String > > ,
57
+ pub timetable_id : i64 ,
58
+ pub study_id : i64 ,
59
+ #[ diesel( sql_type = BigInt ) ]
60
+ pub trains_count : i64 ,
61
+ #[ diesel( sql_type = Text ) ]
62
+ pub infra_name : String ,
63
+ }
64
+
65
+ impl ScenarioV2 {
66
+ pub async fn with_details ( self , db_pool : Data < DbPool > ) -> Result < ScenarioWithDetails > {
67
+ let mut conn = db_pool. get ( ) . await ?;
68
+ self . with_details_conn ( & mut conn) . await
69
+ }
70
+
71
+ pub async fn with_details_conn ( self , conn : & mut PgConnection ) -> Result < ScenarioWithDetails > {
72
+ use crate :: tables:: electrical_profile_set:: dsl as elec_dsl;
73
+ use crate :: tables:: infra:: dsl as infra_dsl;
74
+ use crate :: tables:: train_schedule:: dsl:: * ;
75
+
76
+ let infra_name = infra_dsl:: infra
77
+ . filter ( infra_dsl:: id. eq ( self . infra_id . unwrap ( ) ) )
78
+ . select ( infra_dsl:: name)
79
+ . first :: < String > ( conn)
80
+ . await ?;
81
+
82
+ let electrical_profile_set_name = match self . electrical_profile_set_id . unwrap ( ) {
83
+ Some ( electrical_profile_set) => Some (
84
+ elec_dsl:: electrical_profile_set
85
+ . filter ( elec_dsl:: id. eq ( electrical_profile_set) )
86
+ . select ( elec_dsl:: name)
87
+ . first :: < String > ( conn)
88
+ . await ?,
89
+ ) ,
90
+ None => None ,
91
+ } ;
92
+
93
+ let train_schedules = train_schedule
94
+ . filter ( timetable_id. eq ( self . timetable_id . unwrap ( ) ) )
95
+ . select ( ( id, train_name, departure_time, path_id) )
96
+ . load :: < LightTrainSchedule > ( conn)
97
+ . await ?;
98
+
99
+ let trains_count = train_schedules. len ( ) as i64 ;
100
+
101
+ Ok ( ScenarioWithDetails {
102
+ scenario : self ,
103
+ infra_name,
104
+ electrical_profile_set_name,
105
+ train_schedules,
106
+ trains_count,
107
+ } )
108
+ }
109
+ }
110
+
111
+ /// Delete a scenario.
112
+ /// When we delete a scenario, the associated timetable is deleted too.
113
+ #[ async_trait]
114
+ impl Delete for Scenario {
115
+ async fn delete_conn ( conn : & mut PgConnection , scenario_id : i64 ) -> Result < bool > {
116
+ use crate :: tables:: scenario:: dsl as scenario_dsl;
117
+ use crate :: tables:: timetable:: dsl as timetable_dsl;
118
+
119
+ // Delete scenario
120
+ let scenario = match delete ( scenario_dsl:: scenario. filter ( scenario_dsl:: id. eq ( scenario_id) ) )
121
+ . get_result :: < Scenario > ( conn)
122
+ . await
123
+ {
124
+ Ok ( scenario) => scenario,
125
+ Err ( DieselError :: NotFound ) => return Ok ( false ) ,
126
+ Err ( err) => return Err ( err. into ( ) ) ,
127
+ } ;
128
+
129
+ // Delete timetable
130
+ delete (
131
+ timetable_dsl:: timetable. filter ( timetable_dsl:: id. eq ( scenario. timetable_id . unwrap ( ) ) ) ,
132
+ )
133
+ . execute ( conn)
134
+ . await ?;
135
+ Ok ( true )
136
+ }
137
+ }
138
+
139
+ #[ async_trait]
140
+ impl List < ( i64 , Ordering ) > for ScenarioWithCountTrains {
141
+ /// List all scenarios with the number of trains.
142
+ /// This functions takes a study_id to filter scenarios.
143
+ async fn list_conn (
144
+ conn : & mut PgConnection ,
145
+ page : i64 ,
146
+ page_size : i64 ,
147
+ params : ( i64 , Ordering ) ,
148
+ ) -> Result < PaginatedResponse < Self > > {
149
+ let study_id = params. 0 ;
150
+ let ordering = params. 1 . to_sql ( ) ;
151
+ sql_query ( format ! ( "WITH scenarios_with_train_counts AS (
152
+ SELECT t.*, COUNT(train_schedule.id) as trains_count
153
+ FROM scenario as t
154
+ LEFT JOIN train_schedule ON t.timetable_id = train_schedule.timetable_id WHERE t.study_id = $1
155
+ GROUP BY t.id ORDER BY {ordering}
156
+ )
157
+ SELECT scenarios_with_train_counts.*, infra.name as infra_name
158
+ FROM scenarios_with_train_counts
159
+ JOIN infra ON infra.id = infra_id" ) )
160
+ . bind :: < BigInt , _ > ( study_id)
161
+ . paginate ( page, page_size)
162
+ . load_and_count ( conn) . await
163
+ }
164
+ }
165
+
166
+ #[ cfg( test) ]
167
+ pub mod test {
168
+ use super :: * ;
169
+ use crate :: fixtures:: tests:: { db_pool, scenario_fixture_set, ScenarioFixtureSet , TestFixture } ;
170
+ use crate :: models:: Delete ;
171
+ use crate :: models:: List ;
172
+ use crate :: models:: Ordering ;
173
+ use crate :: models:: Retrieve ;
174
+ use crate :: models:: Timetable ;
175
+ use rstest:: rstest;
176
+
177
+ #[ rstest]
178
+ async fn create_delete_scenario ( db_pool : Data < DbPool > ) {
179
+ let ScenarioFixtureSet { scenario, .. } = scenario_fixture_set ( ) . await ;
180
+
181
+ // Delete the scenario
182
+ Scenario :: delete ( db_pool. clone ( ) , scenario. id ( ) )
183
+ . await
184
+ . unwrap ( ) ;
185
+
186
+ // Second delete should fail
187
+ assert ! ( !Scenario :: delete( db_pool. clone( ) , scenario. id( ) )
188
+ . await
189
+ . unwrap( ) ) ;
190
+ }
191
+
192
+ #[ rstest]
193
+ async fn get_study ( db_pool : Data < DbPool > ) {
194
+ let ScenarioFixtureSet { study, .. } = scenario_fixture_set ( ) . await ;
195
+
196
+ // Get a scenario
197
+ assert ! ( Scenario :: retrieve( db_pool. clone( ) , study. id( ) )
198
+ . await
199
+ . is_ok( ) ) ;
200
+ assert ! ( ScenarioWithCountTrains :: list(
201
+ db_pool. clone( ) ,
202
+ 1 ,
203
+ 25 ,
204
+ ( study. id( ) , Ordering :: LastModifiedAsc )
205
+ )
206
+ . await
207
+ . is_ok( ) ) ;
208
+ }
209
+
210
+ #[ rstest]
211
+ async fn sort_scenario ( db_pool : Data < DbPool > ) {
212
+ let ScenarioFixtureSet {
213
+ scenario,
214
+ study,
215
+ timetable,
216
+ ..
217
+ } = scenario_fixture_set ( ) . await ;
218
+
219
+ // Create second timetable
220
+ let timetable_2 = TestFixture :: create_legacy (
221
+ Timetable {
222
+ id : None ,
223
+ name : Some ( timetable. model . name . clone ( ) . unwrap ( ) + "_bis" ) ,
224
+ } ,
225
+ db_pool. clone ( ) ,
226
+ )
227
+ . await ;
228
+
229
+ // Create second scenario
230
+ let scenario_2 = Scenario {
231
+ name : Some ( scenario. model . name . clone ( ) . unwrap ( ) + "_bis" ) ,
232
+ id : None ,
233
+ timetable_id : Some ( timetable_2. id ( ) ) ,
234
+ ..scenario. model . clone ( )
235
+ } ;
236
+ let _scenario_2 = TestFixture :: create_legacy ( scenario_2, db_pool. clone ( ) ) ;
237
+
238
+ let scenarios =
239
+ ScenarioWithCountTrains :: list ( db_pool. clone ( ) , 1 , 25 , ( study. id ( ) , Ordering :: NameDesc ) )
240
+ . await
241
+ . unwrap ( )
242
+ . results ;
243
+
244
+ for ( p1, p2) in scenarios. iter ( ) . zip ( scenarios. iter ( ) . skip ( 1 ) ) {
245
+ let name_1 = p1. scenario . name . as_ref ( ) . unwrap ( ) . to_lowercase ( ) ;
246
+ let name_2 = p2. scenario . name . as_ref ( ) . unwrap ( ) . to_lowercase ( ) ;
247
+ assert ! ( name_1. ge( & name_2) ) ;
248
+ }
249
+ }
250
+ }
0 commit comments