Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b88baab
Change timezone
Ke-vin-S May 5, 2025
6a4f083
Clients for other apps
Ke-vin-S May 5, 2025
11901a0
Remove all logic files
Ke-vin-S May 6, 2025
49428df
Input model for VRP solver class
Ke-vin-S May 8, 2025
92f98ae
fix
Ke-vin-S May 8, 2025
ba38712
VRP solver with
Ke-vin-S May 8, 2025
2c7c913
Removed dropping deliveries
Ke-vin-S May 8, 2025
3089f2f
more tests for vrp solver
Ke-vin-S May 8, 2025
22ca5a0
Restore dijkstra's algorithm
Ke-vin-S May 8, 2025
c4b7e55
Merge pull request #30 from IASSCMS/refactor/optimizer
Ke-vin-S May 8, 2025
5748787
Update route_optimizer/services/vrp_solver.py
Ke-vin-S May 8, 2025
5145400
Merge pull request #31 from IASSCMS/main
Ke-vin-S May 8, 2025
77215c7
change shipment model
Ke-vin-S May 8, 2025
81e106e
update assignment model
Ke-vin-S May 8, 2025
db4eca9
Delete clients for apps in this project
Ke-vin-S May 8, 2025
d3b9424
Add demand field to shipment
Ke-vin-S May 8, 2025
863361e
Add depot fields to vehicle model
Ke-vin-S May 8, 2025
5561239
Assignment service
Ke-vin-S May 8, 2025
6c726d3
Merge pull request #32 from IASSCMS/feature/assign_shipment
Ke-vin-S May 8, 2025
2660d53
- get drivers and update their status endpoints
Ke-vin-S May 8, 2025
7238c01
Change vehicle status after assigning a task
Ke-vin-S May 9, 2025
6eef678
Added role to assignment_item so the pickup or delivery status can be…
Ke-vin-S May 9, 2025
a641724
Get assignment by vehicle id
Ke-vin-S May 9, 2025
7ab6350
Endpoint for report when reaching endpoint
Ke-vin-S May 9, 2025
445b791
Endpoint to confirm pickup and delivery
Ke-vin-S May 9, 2025
01c646b
Update assignment/services/assignment_planner.py
Ke-vin-S May 9, 2025
63b74e1
Update assignment/serializers/assignment.py
Ke-vin-S May 9, 2025
0e93e52
Update assignment/tests/test_assignment_api.py
Ke-vin-S May 9, 2025
f1e6dd3
Update assignment/models/assignment_item.py
Ke-vin-S May 9, 2025
b14fcf3
Merge pull request #34 from IASSCMS/api/driver-operations
Ke-vin-S May 9, 2025
1109391
Moved assignment endpoints to /api/assignments
Ke-vin-S May 9, 2025
13f7653
Merge pull request #35 from IASSCMS/fix/assignment-path
Ke-vin-S May 9, 2025
2361474
Added cors middleware with allowed host to localhost:4200
moonlander101 May 9, 2025
8fd54d3
Dockerfile set up
Ke-vin-S May 10, 2025
7bf66dc
Set up environment
Ke-vin-S May 10, 2025
0f604d7
Update .github/workflows/tests.yml
Ke-vin-S May 10, 2025
ce93b31
Remove unused statement in workflow
Ke-vin-S May 10, 2025
60b2028
Merge remote-tracking branch 'origin/feature/dockerize' into feature/…
Ke-vin-S May 10, 2025
ce1b112
Remove unused statement in workflow
Ke-vin-S May 10, 2025
adf015c
Merge pull request #37 from IASSCMS/feature/dockerize
Ke-vin-S May 10, 2025
6b6abb2
Update README.md
Ke-vin-S May 10, 2025
9fbc18c
fix
Ke-vin-S May 10, 2025
e5ce8db
Merge pull request #38 from IASSCMS/fix/env
Ke-vin-S May 10, 2025
d4a6648
fix
Ke-vin-S May 10, 2025
f87568b
Merge pull request #40 from IASSCMS/fix/env
Ke-vin-S May 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DJANGO_PORT=8000
LOGISTICS_SERVICE_PORT=8002
KAFKA_BROKER_URL=kafka:9092
IS_DOCKER=True
ALLOWED_HOSTS=localhost,logistics_service,api_gateway
7 changes: 1 addition & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Wait for Kafka to be ready
run: |
echo "Waiting for Kafka..."
sleep 20 # adjust based on startup time
pip install --no-cache-dir -r requirements.txt

- name: Run Django tests (including Kafka)
run: |
Expand Down
36 changes: 36 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Use official Python slim image
FROM python:3.12-slim

LABEL maintainer="kevin"

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV DJANGO_PORT=8000

# Set working directory
WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
curl \
&& apt-get clean && rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy project files
COPY . .

# Copy entrypoint script
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# Expose port (match DJANGO_PORT env)
EXPOSE ${DJANGO_PORT}

# Run entrypoint script
ENTRYPOINT ["/entrypoint.sh"]
167 changes: 107 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,103 +1,150 @@
# Logistics

- route_optimizer/ (Standalone service)
- Inputs: delivery locations, vehicle capacities
- Output: optimized delivery route
- Purpose: The brain of the logistics module
- Develop this first so you can test assignment logic early

- map_service/ (Optional)
- Utility/service to fetch real-world distances
- Can be a local function or API-based (OpenRouteService / OpenStreetMap)
- Can be skipped initially, use dummy distance matrix

- fleet/ (Django app)
- Models: Vehicle, Status, Capacity, Location
- REST APIs to get available vehicles, update location/status
- You’ll need this to match vehicles with optimized route

- assignment/ (Django app)
- Inputs: optimized route + available vehicles (from fleet)
- Logic: assign deliveries to vehicle
- Output: assignment events, persisted records
- Triggers optimizer and manages dispatching

- scheduler/ (Lambda or Celery)
- Automatically triggers assignment + route_optimizer daily/hourly
- Optional for early dev, but crucial for automation

- monitoring/ (Django app)
- Captures logs, alerts, failed deliveries, delays
- Optional dashboard with charts and status
- Could connect with Kafka or DB log events from assignment
- shipments/ (Django app)
- Models: Shipment (order_id, origin, destination, status)
- Status Lifecycle: pending → scheduled → dispatched → in_transit → delivered/failed
- APIs: Create shipment, update status, track delivery progress
- Decoupled from Warehouse via primitive IDs (warehouse_id)
- Triggered by Order events (async/REST), manages physical movement of goods
# 🚚 Logistics Microservice Suite

This service powers intelligent shipment routing, assignment, fleet matching, and delivery monitoring — driven by Kafka and Django.

---
# Getting Started
### 1. ✅ Clone the Repository

## 📦 Modules Overview

- **route_optimizer/**: Optimizes delivery routes (independent service)
- **fleet/**: Manages vehicle data and availability
- **assignment/**: Assigns optimized routes to vehicles
- **scheduler/**: Triggers assignment logic periodically (future: Celery/Lambda)
- **monitoring/**: Logs delivery issues, performance (optional)
- **shipments/**: Manages shipment lifecycle
- **map_service/** *(optional)*: Calculates real-world distances via OpenRouteService or dummy matrix

## 🚀 Getting Started

### 🐳 Option A: Run with Docker (Recommended)

#### 1. Clone the Repo

```bash
git clone https://github.com/IASSCMS/Logistics.git
cd Logistics
```
````

---
#### 2. Create `.env` File

```env
# .env
DJANGO_PORT=8000
KAFKA_BROKER_URL=kafka:9092
LOGISTICS_SERVICE_PORT=8002
```

### 2. 🐍 Create & Activate Virtual Environment
#### 3. Start All Services

#### On Linux/macOS:
```bash
python3 -m venv venv
source venv/bin/activate
docker-compose up --build
```

#### On Windows:
This spins up:

* Django app
* Kafka + Zookeeper

#### 4. Visit in Browser

* Swagger docs: [http://localhost:8002/swagger/](http://localhost:8002/swagger/)
* Admin panel: [http://localhost:8002/admin/](http://localhost:8002/admin/)

#### 5. Run Django Tests in Docker

```bash
python -m venv venv
venv\Scripts\activate
docker-compose run --rm logistics-service python manage.py test
```

---

### 3. 📦 Install Dependencies
### 🐍 Option B: Local Dev Setup (Without Docker)

Make sure your virtual environment is activated, then run:
#### 1. Create & Activate Virtual Environment

```bash
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
```

#### 2. Install Dependencies

```bash
pip install -r requirements.txt
```

---
#### 3. Setup Environment

Set these in `.env` or shell:

### 4. ⚙️ Apply Migrations
```env
KAFKA_BROKER_URL=localhost:9092
```

#### 4. Run Migrations

```bash
python manage.py migrate
```

---

### 5. 🚦 Run the Development Server
#### 5. Start Django Server

```bash
python manage.py runserver
```

---

### 6. 📚 View API Documentation (Swagger)
## 📬 Kafka Setup Notes

This app connects to Kafka topic `orders.created` via `kafka-python`.
Kafka is provided via `confluentinc/cp-kafka` in `docker-compose.yml`.

* Send test events using `publish_mock_event.py`
* Consumer listens via `shipments.consumers.order_events`

Once the server is running, open your browser and go to:
---

## 📂 Project Structure

```
http://127.0.0.1:8000/swagger/
logistics/
├── logistics_core/ # Django project
├── fleet/ # Vehicle models & APIs
├── shipments/ # Shipment status, tracking
├── assignment/ # Route-to-vehicle mapping
├── monitoring/ # Logs, dashboard, alerts
├── route_optimizer/ # Standalone optimization engine
├── manage.py
├── Dockerfile
├── entrypoint.sh
├── docker-compose.yml
└── requirements.txt
```

You’ll see an interactive **Swagger UI** listing all available API endpoints (e.g., `/api/fleet/vehicles/`).
---

## 📄 API Documentation

Once server is running:

* Swagger UI: [http://localhost:8002/swagger/](http://localhost:8002/swagger/)
* Redoc: [http://localhost:8002/redoc/](http://localhost:8002/redoc/)

---

## 🔐 Admin Account

Create one manually:

```bash
python manage.py createsuperuser
```

Or inside Docker:

```bash
docker-compose exec logistics-service python manage.py createsuperuser
```

---
24 changes: 21 additions & 3 deletions assignment/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 5.2 on 2025-04-23 12:51
# Generated by Django 5.2 on 2025-05-08 10:38

import django.db.models.deletion
from django.db import migrations, models
Expand All @@ -9,7 +9,8 @@ class Migration(migrations.Migration):
initial = True

dependencies = [
('fleet', '0001_initial'),
('fleet', '0003_remove_fuelrecord_vehicle_and_more'),
('shipments', '0002_remove_shipment_destination_warehouse_id_and_more'),
]

operations = [
Expand All @@ -18,9 +19,26 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('delivery_locations', models.JSONField()),
('started_at', models.DateTimeField(blank=True, null=True)),
('completed_at', models.DateTimeField(blank=True, null=True)),
('status', models.CharField(choices=[('created', 'Created'), ('dispatched', 'Dispatched'), ('partially_completed', 'Partially Completed'), ('completed', 'Completed'), ('failed', 'Failed'), ('reassigned', 'Reassigned')], default='created', max_length=32)),
('total_load', models.PositiveIntegerField()),
('vehicle', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fleet.vehicle')),
],
),
migrations.CreateModel(
name='AssignmentItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delivery_sequence', models.PositiveIntegerField()),
('delivery_location', models.JSONField()),
('is_delivered', models.BooleanField(default=False)),
('delivered_at', models.DateTimeField(blank=True, null=True)),
('assignment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='assignment.assignment')),
('shipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shipments.shipment')),
],
options={
'ordering': ['delivery_sequence'],
},
),
]
18 changes: 18 additions & 0 deletions assignment/migrations/0002_assignmentitem_role.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.2 on 2025-05-09 01:18

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('assignment', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='assignmentitem',
name='role',
field=models.CharField(choices=[('pickup', 'Pickup'), ('delivery', 'Delivery')], default='delivery', max_length=10),
),
]
11 changes: 0 additions & 11 deletions assignment/models.py

This file was deleted.

File renamed without changes.
26 changes: 26 additions & 0 deletions assignment/models/assignment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.db import models
from fleet.models import Vehicle

class Assignment(models.Model):
vehicle = models.ForeignKey(Vehicle, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
started_at = models.DateTimeField(null=True, blank=True)
completed_at = models.DateTimeField(null=True, blank=True)

status = models.CharField(
max_length=32,
choices=[
('created', 'Created'),
('dispatched', 'Dispatched'),
('partially_completed', 'Partially Completed'),
('completed', 'Completed'),
('failed', 'Failed'),
('reassigned', 'Reassigned'),
],
default='created'
)

total_load = models.PositiveIntegerField()

def __str__(self):
return f"Assignment #{self.id} to Vehicle {self.vehicle.vehicle_id}"
28 changes: 28 additions & 0 deletions assignment/models/assignment_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.db import models
from assignment.models.assignment import Assignment
from shipments.models import Shipment


class AssignmentItem(models.Model):
ROLE_CHOICES = [
("pickup", "Pickup"),
("delivery", "Delivery"),
]

assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE, related_name='items')
shipment = models.ForeignKey(Shipment, on_delete=models.CASCADE)
delivery_sequence = models.PositiveIntegerField() # 1st, 2nd, 3rd stop, etc.
delivery_location = models.JSONField() # { "lat": ..., "lng": ... }

role = models.CharField(max_length=10, choices=ROLE_CHOICES, default="delivery") # NEW

# TODO: Consider renaming 'is_delivered' and 'delivered_at' for better clarity.
# Example: 'is_delivered' -> 'has_been_delivered', 'delivered_at' -> 'delivery_timestamp'.
is_delivered = models.BooleanField(default=False)
delivered_at = models.DateTimeField(null=True, blank=True)

class Meta:
ordering = ['delivery_sequence']

def __str__(self):
return f"{self.role.capitalize()} for Shipment {self.shipment.id} in Assignment {self.assignment.id}"
Loading