11from django .core .exceptions import ValidationError
2- from django .db import models
2+ from django .db import transaction , models
33from typing import List , Dict , Any
44from .models import Trip , TripStop
55from orders .models import Stop , Order
66
77
88class TripValidationError (ValidationError ):
99 """Custom validation error for trip-related violations"""
10+
1011 pass
1112
1213
@@ -50,7 +51,7 @@ def validate_new_trip_stop(trip: Trip, stop: Stop) -> None:
5051 existing_stop_ids = set (ts .stop .id for ts in trip_stops )
5152
5253 # Get the paired stop (pickup if this is delivery, delivery if this is pickup)
53- paired_stop_type = ' pickup' if stop .stop_type == ' delivery' else ' delivery'
54+ paired_stop_type = " pickup" if stop .stop_type == " delivery" else " delivery"
5455 paired_stop = order .stops .filter (stop_type = paired_stop_type ).first ()
5556
5657 if not paired_stop :
@@ -78,25 +79,25 @@ def get_incomplete_orders(trip: Trip) -> List[Order]:
7879 Returns:
7980 List of Order instances that are missing either pickup or delivery stops
8081 """
81- trip_stops = trip .trip_stops .select_related (' stop' , ' stop__order' ).all ()
82+ trip_stops = trip .trip_stops .select_related (" stop" , " stop__order" ).all ()
8283 orders_in_trip = {}
8384
8485 # Group stops by order
8586 for trip_stop in trip_stops :
8687 if trip_stop .stop .order :
8788 order = trip_stop .stop .order
8889 if order .id not in orders_in_trip :
89- orders_in_trip [order .id ] = {' order' : order , ' stops' : []}
90- orders_in_trip [order .id ][' stops' ].append (trip_stop .stop )
90+ orders_in_trip [order .id ] = {" order" : order , " stops" : []}
91+ orders_in_trip [order .id ][" stops" ].append (trip_stop .stop )
9192
9293 incomplete_orders = []
9394 for order_data in orders_in_trip .values ():
94- order = order_data [' order' ]
95- stops = order_data [' stops' ]
95+ order = order_data [" order" ]
96+ stops = order_data [" stops" ]
9697 stop_types = {stop .stop_type for stop in stops }
9798
9899 # Check if both pickup and delivery are present
99- if ' pickup' not in stop_types or ' delivery' not in stop_types :
100+ if " pickup" not in stop_types or " delivery" not in stop_types :
100101 incomplete_orders .append (order )
101102
102103 return incomplete_orders
@@ -116,8 +117,8 @@ def ensure_order_pair_in_trip(trip: Trip, order: Order) -> Dict[str, Any]:
116117 Raises:
117118 TripValidationError: If the order doesn't have both pickup and delivery stops
118119 """
119- pickup_stop = order .stops .filter (stop_type = ' pickup' ).first ()
120- delivery_stop = order .stops .filter (stop_type = ' delivery' ).first ()
120+ pickup_stop = order .stops .filter (stop_type = " pickup" ).first ()
121+ delivery_stop = order .stops .filter (stop_type = " delivery" ).first ()
121122
122123 if not pickup_stop :
123124 raise TripValidationError (
@@ -129,13 +130,12 @@ def ensure_order_pair_in_trip(trip: Trip, order: Order) -> Dict[str, Any]:
129130 f"Order { order .order_number } does not have a delivery stop."
130131 )
131132
132- return {
133- 'pickup_stop' : pickup_stop ,
134- 'delivery_stop' : delivery_stop
135- }
133+ return {"pickup_stop" : pickup_stop , "delivery_stop" : delivery_stop }
136134
137135
138- def add_order_to_trip (trip : Trip , order : Order , pickup_time , delivery_time , notes : str = "" ) -> Dict [str , Any ]:
136+ def add_order_to_trip (
137+ trip : Trip , order : Order , pickup_time , delivery_time , notes : str = ""
138+ ) -> Dict [str , Any ]:
139139 """
140140 Add both pickup and delivery stops for an order to a trip atomically.
141141
@@ -157,14 +157,17 @@ def add_order_to_trip(trip: Trip, order: Order, pickup_time, delivery_time, note
157157
158158 # Validate the order has both stops
159159 order_stops = ensure_order_pair_in_trip (trip , order )
160- pickup_stop = order_stops [' pickup_stop' ]
161- delivery_stop = order_stops [' delivery_stop' ]
160+ pickup_stop = order_stops [" pickup_stop" ]
161+ delivery_stop = order_stops [" delivery_stop" ]
162162
163163 with transaction .atomic ():
164164 # Get the next available sequence numbers for the trip
165- last_sequence = trip .trip_stops .aggregate (
166- max_sequence = models .Max ('sequence' )
167- )['max_sequence' ] or 0
165+ last_sequence = (
166+ trip .trip_stops .aggregate (max_sequence = models .Max ("sequence" ))[
167+ "max_sequence"
168+ ]
169+ or 0
170+ )
168171
169172 pickup_sequence = last_sequence + 1
170173 delivery_sequence = last_sequence + 2
@@ -175,7 +178,7 @@ def add_order_to_trip(trip: Trip, order: Order, pickup_time, delivery_time, note
175178 stop = pickup_stop ,
176179 sequence = pickup_sequence ,
177180 planned_arrival_time = pickup_time ,
178- notes = notes or f' Pickup for { order .order_number } '
181+ notes = notes or f" Pickup for { order .order_number } " ,
179182 )
180183 pickup_trip_stop .save (skip_validation = True )
181184
@@ -184,13 +187,13 @@ def add_order_to_trip(trip: Trip, order: Order, pickup_time, delivery_time, note
184187 stop = delivery_stop ,
185188 sequence = delivery_sequence ,
186189 planned_arrival_time = delivery_time ,
187- notes = notes or f' Delivery for { order .order_number } '
190+ notes = notes or f" Delivery for { order .order_number } " ,
188191 )
189192 delivery_trip_stop .save (skip_validation = True )
190193
191194 return {
192- ' pickup_trip_stop' : pickup_trip_stop ,
193- ' delivery_trip_stop' : delivery_trip_stop
195+ " pickup_trip_stop" : pickup_trip_stop ,
196+ " delivery_trip_stop" : delivery_trip_stop ,
194197 }
195198
196199
@@ -204,38 +207,46 @@ def validate_pickup_before_delivery(trip: Trip) -> None:
204207 Raises:
205208 TripValidationError: If any order has delivery before pickup
206209 """
207- trip_stops = trip .trip_stops .select_related ('stop' , 'stop__order' ).order_by ('sequence' )
210+ trip_stops = trip .trip_stops .select_related ("stop" , "stop__order" ).order_by (
211+ "sequence"
212+ )
208213 orders_in_trip = {}
209214
210215 # Group stops by order and track their positions
211216 for trip_stop in trip_stops :
212217 if trip_stop .stop .order :
213218 order = trip_stop .stop .order
214219 if order .id not in orders_in_trip :
215- orders_in_trip [order .id ] = {'order' : order , 'stops' : []}
216- orders_in_trip [order .id ]['stops' ].append ({
217- 'trip_stop' : trip_stop ,
218- 'stop_type' : trip_stop .stop .stop_type ,
219- 'sequence_position' : trip_stop .sequence
220- })
220+ orders_in_trip [order .id ] = {"order" : order , "stops" : []}
221+ orders_in_trip [order .id ]["stops" ].append (
222+ {
223+ "trip_stop" : trip_stop ,
224+ "stop_type" : trip_stop .stop .stop_type ,
225+ "sequence_position" : trip_stop .sequence ,
226+ }
227+ )
221228
222229 # Check each order's pickup-delivery ordering
223230 for order_data in orders_in_trip .values ():
224- order = order_data [' order' ]
225- stops = order_data [' stops' ]
231+ order = order_data [" order" ]
232+ stops = order_data [" stops" ]
226233
227- pickup_positions = [s ['sequence_position' ] for s in stops if s ['stop_type' ] == 'pickup' ]
228- delivery_positions = [s ['sequence_position' ] for s in stops if s ['stop_type' ] == 'delivery' ]
234+ pickup_positions = [
235+ s ["sequence_position" ] for s in stops if s ["stop_type" ] == "pickup"
236+ ]
237+ delivery_positions = [
238+ s ["sequence_position" ] for s in stops if s ["stop_type" ] == "delivery"
239+ ]
229240
230241 # If both pickup and delivery exist, pickup must come before delivery
231242 if pickup_positions and delivery_positions :
232- min_pickup_pos = min (pickup_positions )
233- min_delivery_pos = min (delivery_positions )
243+ max_pickup_pos = max (pickup_positions )
244+ max_delivery_pos = max (delivery_positions )
234245
235- if min_pickup_pos >= min_delivery_pos :
246+ if max_pickup_pos >= max_delivery_pos :
236247 raise TripValidationError (
237- f"Order { order .order_number } has delivery stop (position { min_delivery_pos } ) "
238- f"before or at same position as pickup stop (position { min_pickup_pos } ). "
248+ f"Order { order .order_number } has delivery stop (position { max_delivery_pos } ) "
249+ f"before or at same position as pickup stop (position { max_pickup_pos } ). "
239250 f"Pickup must occur before delivery."
240251 )
241252
@@ -250,11 +261,47 @@ def get_orders_requiring_both_stops() -> List[Order]:
250261 """
251262 orders_with_both_stops = []
252263
253- for order in Order .objects .prefetch_related (' stops' ).all ():
264+ for order in Order .objects .prefetch_related (" stops" ).all ():
254265 stops = order .stops .all ()
255266 stop_types = {stop .stop_type for stop in stops }
256267
257- if ' pickup' in stop_types and ' delivery' in stop_types :
268+ if " pickup" in stop_types and " delivery" in stop_types :
258269 orders_with_both_stops .append (order )
259270
260271 return orders_with_both_stops
272+
273+
274+ def update_trip_stop_sequences (trip : Trip , new_sequences : List [Dict [str , Any ]]) -> None :
275+ """
276+ Update the sequence order of trip stops.
277+
278+ Args:
279+ trip: The Trip instance to update
280+ new_sequences: List of dicts with 'id' and 'sequence' keys
281+
282+ Raises:
283+ TripValidationError: If some trip stops don't belong to this trip
284+ """
285+
286+ with transaction .atomic ():
287+ # Validate that all provided stop IDs belong to this trip
288+ provided_stop_ids = [item ["id" ] for item in new_sequences ]
289+ existing_stops = TripStop .objects .filter (trip = trip , id__in = provided_stop_ids )
290+
291+ if len (existing_stops ) != len (provided_stop_ids ):
292+ raise TripValidationError ("Some trip stops do not belong to this trip" )
293+
294+ # First, set all orders to very high values to avoid constraint conflicts
295+ # Use values starting from 10000 to avoid conflicts with existing orders
296+ for i , sequence_item in enumerate (new_sequences ):
297+ trip_stop = TripStop .objects .get (id = sequence_item ["id" ], trip = trip )
298+ trip_stop .sequence = 10000 + i # Use high values temporarily
299+ trip_stop .save ()
300+
301+ # Then set the final order values
302+ for sequence_item in new_sequences :
303+ trip_stop = TripStop .objects .get (id = sequence_item ["id" ], trip = trip )
304+ sequence_value = sequence_item .get ("sequence" )
305+ if sequence_value is not None :
306+ trip_stop .sequence = sequence_value
307+ trip_stop .save ()
0 commit comments