Skip to content

Commit 38de814

Browse files
cosmithclaude
andcommitted
feat: replace stops with orders system and update UI
- Migrate from stops-based to orders-based data model - Add comprehensive order management with customer details - Update backend API endpoints from /stops/ to /orders/ - Refactor frontend components from StopManagement to OrderManagement - Update map interface to show orders instead of stops - Remove deprecated stop-related code and components - Fix TypeScript import issues in OrderForm component 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent dbbbc1d commit 38de814

35 files changed

Lines changed: 1479 additions & 864 deletions

.claude/commands/commit.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
If there are frontend changes, first run `npm run format` in the frontend. Then run `npm run build` and fix errors.
2+
If there are backend changes, run `uv run ruff check backend/ --fix`
3+
4+
Then create a commit, fix the pre-commit errors and commit.

backend/API_DOC.md

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -196,72 +196,71 @@ All list endpoints return data in this format:
196196
}
197197
```
198198

199-
## Stops
199+
## Orders
200200

201-
### List/Create Stops
202-
- **GET** `/api/stops/` - List all stops
203-
- **POST** `/api/stops/` - Create new stop
201+
### List/Create Orders
202+
- **GET** `/api/orders/` - List all orders
203+
- **POST** `/api/orders/` - Create new order
204204

205-
### Stop Details
206-
- **GET** `/api/stops/{id}/` - Get stop details
207-
- **PUT** `/api/stops/{id}/` - Update stop
208-
- **DELETE** `/api/stops/{id}/` - Delete stop
205+
### Order Details
206+
- **GET** `/api/orders/{id}/` - Get order details
207+
- **PUT** `/api/orders/{id}/` - Update order
208+
- **DELETE** `/api/orders/{id}/` - Delete order
209209

210-
**Stop Object:**
210+
### Generate Fake Orders
211+
- **POST** `/api/orders/generate-fake/` - Generate random test orders
212+
213+
**Order Object:**
211214
```json
212215
{
213216
"id": 1,
214-
"name": "Loading Dock A",
215-
"address": "100 Warehouse St",
216-
"latitude": "41.878113",
217-
"longitude": "-87.629799",
218-
"stop_type": "loading",
219-
"contact_name": "Dock Manager",
220-
"contact_phone": "555-0001",
221-
"notes": "Use rear entrance",
217+
"order_number": "ORD-2024-0001",
218+
"customer_name": "John Smith",
219+
"customer_company": "ACME Manufacturing",
220+
"customer_email": "john.smith@acme.com",
221+
"customer_phone": "+33-1-42-00-1234",
222+
"pickup_stop": {
223+
"id": 1,
224+
"name": "Rungis International Market",
225+
"address": "1 Rue de la Tour, 94150 Rungis, France",
226+
"latitude": "48.759000",
227+
"longitude": "2.352000",
228+
"stop_type": "loading"
229+
},
230+
"delivery_stop": {
231+
"id": 15,
232+
"name": "Carrefour Distribution Paris",
233+
"address": "93 Avenue de Paris, 94300 Vincennes, France",
234+
"latitude": "48.847000",
235+
"longitude": "2.428000",
236+
"stop_type": "unloading"
237+
},
238+
"goods_description": "Fresh produce and dairy products",
239+
"goods_weight": "2500.00",
240+
"goods_volume": "15.00",
241+
"goods_type": "refrigerated",
242+
"special_instructions": "Keep temperature at 2-4°C throughout transport",
243+
"status": "pending",
244+
"requested_pickup_date": "2024-01-20",
245+
"requested_delivery_date": "2024-01-21",
222246
"created_at": "2024-01-15T08:00:00Z",
223247
"updated_at": "2024-01-15T08:00:00Z"
224248
}
225249
```
226250

227-
**Stop Types:** `"loading"` or `"unloading"`
228-
229-
**Coordinates:**
230-
- `latitude` and `longitude` fields are optional decimal values
231-
- When creating stops, coordinates can be provided or omitted (will be `null`)
232-
- The fake stop generator automatically creates realistic coordinates
233-
234-
### Generate Fake Stops
235-
- **POST** `/api/stops/generate-fake/` - Generate 3-8 random stops with faker data
236-
237-
**Response:**
238-
```json
239-
{
240-
"message": "Successfully created 5 new orders",
241-
"created_stops": [
242-
{
243-
"id": 10,
244-
"name": "Warehouse C",
245-
"address": "789 Industrial Blvd, Springfield, IL 62701",
246-
"latitude": "39.781721",
247-
"longitude": "-89.650148",
248-
"stop_type": "loading",
249-
"contact_name": "Mike Johnson",
250-
"contact_phone": "555-0123",
251-
"notes": "Use loading bay 3",
252-
"created_at": "2024-01-15T10:30:00Z",
253-
"updated_at": "2024-01-15T10:30:00Z"
254-
}
255-
]
256-
}
257-
```
251+
**Order Status Options:**
252+
- `"pending"` - Order created, awaiting assignment
253+
- `"assigned"` - Order assigned to a trip
254+
- `"in_transit"` - Order currently being transported
255+
- `"delivered"` - Order completed successfully
256+
- `"cancelled"` - Order cancelled
258257

259-
This endpoint simulates incoming orders by creating random stops with realistic data including:
260-
- Warehouse/distribution centers for loading stops
261-
- Customer sites/stores for unloading stops
262-
- Realistic addresses with city, state, zip
263-
- Contact names and phone numbers
264-
- Optional notes
258+
**Goods Types:**
259+
- `"standard"` - Standard cargo
260+
- `"fragile"` - Fragile items requiring careful handling
261+
- `"hazmat"` - Hazardous materials requiring special permits
262+
- `"refrigerated"` - Temperature-controlled goods
263+
- `"oversized"` - Oversized cargo requiring special equipment
265264

266265
## Trips
267266

@@ -493,12 +492,13 @@ All trip stop IDs in the request must belong to the specified trip. The response
493492
1. **Login:** POST `/api/auth/login/` (get token)
494493
2. **Create Company:** POST `/api/companies/` (with token)
495494
3. **Create Vehicle:** POST `/api/vehicles/` (with company ID and token)
496-
4. **Create Stops:** POST `/api/stops/` (for loading/unloading locations)
495+
4. **Create Orders:** POST `/api/orders/` (customers with pickup/delivery locations)
497496
5. **Create Trip:** POST `/api/trips/` (with vehicle and dispatcher)
498497
6. **Add Stops to Trip:** POST `/api/trip-stops/` (with trip, stop, and order)
499498
7. **Notify Driver:** POST `/api/trips/{id}/notify-driver/`
500499
8. **Update Trip Status:** PUT `/api/trips/{id}/` (change status to "planned")
501-
9. **Logout:** POST `/api/auth/logout/` (invalidate token)
500+
9. **Update Order Status:** PUT `/api/orders/{id}/` (track order progress)
501+
10. **Logout:** POST `/api/auth/logout/` (invalidate token)
502502

503503
## Positions (Telematics Data)
504504

backend/dashmap/management/commands/reset_db.py

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
import random
66
from companies.models import Company
77
from vehicles.models import Vehicle
8-
from trips.models import Stop, Trip, TripStop
8+
from trips.models import Trip, TripStop
9+
from orders.models import Stop, Order
910
from positions.models import Position
1011
from accounts.models import AuthToken, UserProfile
1112

@@ -226,6 +227,99 @@ def handle(self, *args, **options):
226227

227228
self.stdout.write(f'Created {len(stops)} test stops')
228229

230+
# Create test orders
231+
self.stdout.write('Creating test orders...')
232+
loading_stops = [s for s in stops if s.stop_type == 'loading']
233+
unloading_stops = [s for s in stops if s.stop_type == 'unloading']
234+
235+
orders_data = [
236+
{
237+
'customer_name': 'ACME Manufacturing',
238+
'customer_company': 'ACME Corp',
239+
'customer_email': 'logistics@acme.com',
240+
'customer_phone': '+33-1-42-00-1234',
241+
'pickup_stop': loading_stops[0], # Rungis
242+
'delivery_stop': unloading_stops[0], # Carrefour Paris
243+
'goods_description': 'Fresh produce and dairy products',
244+
'goods_weight': 2500.0,
245+
'goods_volume': 15.0,
246+
'goods_type': 'refrigerated',
247+
'special_instructions': 'Keep temperature at 2-4°C throughout transport'
248+
},
249+
{
250+
'customer_name': 'TechCorp Europe',
251+
'customer_company': 'TechCorp Ltd',
252+
'customer_email': 'supply@techcorp.eu',
253+
'customer_phone': '+33-1-45-67-8900',
254+
'pickup_stop': loading_stops[4], # Toulouse Aerospace
255+
'delivery_stop': unloading_stops[7], # Frankfurt
256+
'goods_description': 'Aerospace electronic components',
257+
'goods_weight': 850.0,
258+
'goods_volume': 5.2,
259+
'goods_type': 'fragile',
260+
'special_instructions': 'Handle with extreme care - sensitive electronics'
261+
},
262+
{
263+
'customer_name': 'EuroConstruction',
264+
'customer_company': 'EuroConstruction SA',
265+
'customer_email': 'orders@euroconstruct.fr',
266+
'customer_phone': '+33-4-78-90-1234',
267+
'pickup_stop': loading_stops[8], # Saint-Gobain Melun
268+
'delivery_stop': unloading_stops[4], # Brussels
269+
'goods_description': 'Construction materials and tools',
270+
'goods_weight': 4200.0,
271+
'goods_volume': 28.0,
272+
'goods_type': 'standard',
273+
'special_instructions': 'Delivery to construction site - crane available'
274+
},
275+
{
276+
'customer_name': 'PharmaLogistics',
277+
'customer_company': 'PharmaDistrib',
278+
'customer_email': 'urgent@pharmadistrib.com',
279+
'customer_phone': '+33-1-56-78-9012',
280+
'pickup_stop': loading_stops[9], # Sanofi
281+
'delivery_stop': unloading_stops[8], # Milan
282+
'goods_description': 'Pharmaceutical supplies and medications',
283+
'goods_weight': 320.0,
284+
'goods_volume': 2.8,
285+
'goods_type': 'hazmat',
286+
'special_instructions': 'Requires temperature monitoring and hazmat certification'
287+
},
288+
{
289+
'customer_name': 'Luxury Retail Chain',
290+
'customer_company': 'EliteStores International',
291+
'customer_email': 'procurement@elitestores.com',
292+
'customer_phone': '+33-1-44-55-6677',
293+
'pickup_stop': loading_stops[10], # LVMH Logistics
294+
'delivery_stop': unloading_stops[10], # Zurich
295+
'goods_description': 'Luxury fashion and accessories',
296+
'goods_weight': 180.0,
297+
'goods_volume': 8.5,
298+
'goods_type': 'fragile',
299+
'special_instructions': 'High-value goods - requires secure transport and signature on delivery'
300+
}
301+
]
302+
303+
orders = []
304+
for o_data in orders_data:
305+
order = Order.objects.create(
306+
customer_name=o_data['customer_name'],
307+
customer_company=o_data['customer_company'],
308+
customer_email=o_data['customer_email'],
309+
customer_phone=o_data['customer_phone'],
310+
pickup_stop=o_data['pickup_stop'],
311+
delivery_stop=o_data['delivery_stop'],
312+
goods_description=o_data['goods_description'],
313+
goods_weight=o_data['goods_weight'],
314+
goods_volume=o_data['goods_volume'],
315+
goods_type=o_data['goods_type'],
316+
special_instructions=o_data['special_instructions'],
317+
status='pending'
318+
)
319+
orders.append(order)
320+
321+
self.stdout.write(f'Created {len(orders)} test orders')
322+
229323
# Create test trips
230324
self.stdout.write('Creating test trips...')
231325
today = timezone.now().date()
@@ -398,12 +492,12 @@ def handle(self, *args, **options):
398492
self.stdout.write(f'Auth Token: {token.key}')
399493
self.stdout.write(f'Companies: {Company.objects.count()}')
400494
self.stdout.write(f'Vehicles: {Vehicle.objects.count()}')
401-
self.stdout.write(f'Stops: {Stop.objects.count()}')
495+
self.stdout.write(f'Orders: {Order.objects.count()}')
402496
self.stdout.write(f'Trips: {Trip.objects.count()}')
403497
self.stdout.write(f'Positions: {Position.objects.count()}')
404498
self.stdout.write('\nTest data includes:')
405499
self.stdout.write('• Dashmove company with 20 heavy trucks (18-40 tons capacity)')
406-
self.stdout.write('• 62 European stops (ports, warehouses, factories) across France and neighboring countries')
500+
self.stdout.write('• 5 sample orders with customer details, pickup/delivery locations, and goods information')
407501
self.stdout.write('• 10 sample trips with different statuses')
408502
self.stdout.write('• 48 hours of position data for 10 vehicles across European routes')
409503
self.stdout.write('• Realistic heavy truck logistics operations')

backend/dashmap/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
'accounts',
4343
'companies',
4444
'vehicles',
45+
'orders',
4546
'trips',
4647
'positions',
4748
]

backend/dashmap/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
path('api/', include('accounts.urls')),
2323
path('api/', include('companies.urls')),
2424
path('api/', include('vehicles.urls')),
25+
path('api/', include('orders.urls')),
2526
path('api/', include('trips.urls')),
2627
path('api/', include('positions.urls')),
2728
]

backend/orders/__init__.py

Whitespace-only changes.

backend/orders/admin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.contrib import admin
2+
3+
# Register your models here.

backend/orders/apps.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class OrdersConfig(AppConfig):
5+
default_auto_field = 'django.db.models.BigAutoField'
6+
name = 'orders'
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Generated by Django 5.2.5 on 2025-09-05 10:23
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
initial = True
10+
11+
dependencies = [
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='Stop',
17+
fields=[
18+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19+
('name', models.CharField(max_length=200)),
20+
('address', models.TextField()),
21+
('latitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
22+
('longitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
23+
('stop_type', models.CharField(choices=[('loading', 'Loading'), ('unloading', 'Unloading')], max_length=10)),
24+
('contact_name', models.CharField(blank=True, max_length=100)),
25+
('contact_phone', models.CharField(blank=True, max_length=20)),
26+
('notes', models.TextField(blank=True)),
27+
('created_at', models.DateTimeField(auto_now_add=True)),
28+
('updated_at', models.DateTimeField(auto_now=True)),
29+
],
30+
),
31+
migrations.CreateModel(
32+
name='Order',
33+
fields=[
34+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
35+
('customer_name', models.CharField(max_length=200)),
36+
('customer_company', models.CharField(blank=True, max_length=200)),
37+
('customer_email', models.EmailField(blank=True, max_length=254)),
38+
('customer_phone', models.CharField(blank=True, max_length=20)),
39+
('goods_description', models.TextField()),
40+
('goods_weight', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
41+
('goods_volume', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
42+
('goods_type', models.CharField(choices=[('standard', 'Standard'), ('fragile', 'Fragile'), ('hazmat', 'Hazardous Materials'), ('refrigerated', 'Refrigerated'), ('oversized', 'Oversized')], default='standard', max_length=20)),
43+
('special_instructions', models.TextField(blank=True)),
44+
('order_number', models.CharField(max_length=50, unique=True)),
45+
('status', models.CharField(choices=[('pending', 'Pending'), ('assigned', 'Assigned'), ('in_transit', 'In Transit'), ('delivered', 'Delivered'), ('cancelled', 'Cancelled')], default='pending', max_length=20)),
46+
('requested_pickup_date', models.DateField(blank=True, null=True)),
47+
('requested_delivery_date', models.DateField(blank=True, null=True)),
48+
('created_at', models.DateTimeField(auto_now_add=True)),
49+
('updated_at', models.DateTimeField(auto_now=True)),
50+
('delivery_stop', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='delivery_orders', to='orders.stop')),
51+
('pickup_stop', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='pickup_orders', to='orders.stop')),
52+
],
53+
),
54+
]

backend/orders/migrations/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)