# Delivery System — How It Works

Module path: `DeliveryApp/Delivery/`

---

## Overview

The delivery system handles cost calculation, shipment creation, status tracking, and webhook processing for multiple courier providers (Pathao, Redx, Steadfast, Sundarban, Manual). Each provider is a **driver class** registered in `config/delivery.php`. The admin configures providers through the UI; the system resolves the right driver at runtime.

---

## Architecture

```
config/delivery.php          ← registers slug → driver class map
      │
      ▼
DeliveryDriverRegistry       ← resolves DeliveryProvider model → driver instance
      │
      ▼
DeliveryProviderContract     ← interface all drivers implement
      │
   ┌──┴──────────────────────────────────┐
   │  PathaoDriver  RedxDriver  ...      │  ← one class per provider
   └─────────────────────────────────────┘
```

---

## 1. Configuration (`config/delivery.php`)

```php
'drivers' => [
    'pathao'    => PathaoDriver::class,
    'redx'      => RedxDriver::class,
    'steadfast' => SteadfastDriver::class,
    'sundarban' => SundarbanDriver::class,
    'manual'    => ManualDriver::class,
],

'cache_ttl_hours'   => 6,     // how long cost cache stays valid
'precalculate_zones' => [...], // zones warmed by artisan command
'status_map'         => [...], // maps provider status strings → internal status
```

To add a new provider driver, add its slug and class here. No other config file needs changing.

---

## 2. Database Tables

| Table | Purpose |
|-------|---------|
| `delivery_providers` | Admin-managed provider records (name, slug, credentials, config) |
| `delivery_settings` | One row per tenant — business address, default provider |
| `product_delivery_settings` | Per-product weight/dimensions override |
| `delivery_cost_cache` | Cached cost results (expires after `cache_ttl_hours`) |
| `delivery_orders` | Delivery state per order (tracking ID, status, address) |
| `delivery_status_logs` | Full audit trail of every status change |
| `delivery_webhook_logs` | Raw webhook payloads logged before processing |
| `deliveries` | Legacy general-purpose delivery records (title, status, provider) |

---

## 3. Driver Contract

Every driver must implement `DeliveryProviderContract`:

```php
interface DeliveryProviderContract
{
    public function calculateCost(DeliveryPayload $payload): DeliveryCostResult;
    public function createShipment(Order $order): ShipmentResult;
    public function trackShipment(string $trackingId): TrackingResult;
    public function handleWebhook(Request $request): void;
    public function verifyWebhookSignature(Request $request): bool;
}
```

Each driver receives its `DeliveryProvider` model in the constructor, giving it access to `credentials` and `config` JSON fields configured in the UI.

---

## 4. Adding a New Provider

### Step 1 — Create the driver class

```
DeliveryApp/Delivery/app/Drivers/Delivery/MyProviderDriver.php
```

Implement all 5 methods. Access API credentials via `$this->provider->credentials['api_key']`, etc.

### Step 2 — Register in config

```php
// config/delivery.php
'drivers' => [
    ...
    'myprovider' => \DeliveryApp\Delivery\Drivers\Delivery\MyProviderDriver::class,
],
```

### Step 3 — Create the DB record via admin UI

Go to **Delivery → Providers → Create** is not available (providers are pre-seeded from DB). Instead, open the Providers index page and any existing provider with the matching slug will auto-resolve to your driver.

> The system matches `delivery_providers.slug` to the `drivers` config key. If the slug doesn't match any key in config, a `RuntimeException` is thrown.

---

## 5. Cost Calculation Flow

```
GET /api/delivery/cost?product_id=1&district=Dhaka&postcode=1000

      DeliveryCostController
            │
      DeliveryCostCalculationService::calculate()
            │
      1. Resolve provider:
         - check ProductDeliverySetting for product-specific override
         - fall back to DeliverySetting::instance()->default_provider_id
            │
      2. Check DeliveryCostCache (expires_at > now)
         → cache hit: return cached result immediately
            │
      3. Cache miss:
         - Build DeliveryPayload from product dimensions + origin address
         - Call driver->calculateCost($payload)
         - Save result to DeliveryCostCache
            │
      Return DeliveryCostResult { cost, estimatedDays, meta }
```

### Pre-warming the cache

```bash
php artisan delivery:precalculate-costs
```

Runs all products × all zones defined in `precalculate_zones`. Useful to call after a provider credentials change.

---

## 6. Order Delivery Flow

```
Order created
      │
Admin dispatches → POST orders/{order}/delivery/status
      │
OrderDeliveryService::updateStatus()
      │
  1. Write to delivery_status_logs (from_status, to_status, source, extra_charge)
  2. Update delivery_orders.delivery_status
  3. Fire DeliveryStatusUpdated event
      │
SendDeliveryStatusNotification listener → notifies customer
```

### Change provider mid-order

```
PUT orders/{order}/delivery  { provider_id: X }

OrderDeliveryService::changeProvider()
  → updates delivery_orders.delivery_provider_id
  → recalculates delivery_cost for the new provider
```

---

## 7. Webhooks

Each provider posts status updates to:

```
POST /webhooks/delivery/{slug}
```

Where `{slug}` matches the provider's slug (e.g. `pathao`, `redx`).

Processing flow:

```
DeliveryCallbackController
      │
  1. Log raw payload to delivery_webhook_logs (status: received)
  2. Resolve driver by slug
  3. driver->verifyWebhookSignature($request)  → 403 on failure
  4. driver->handleWebhook($request)
     → map provider status via config('delivery.status_map.{slug}')
     → call OrderDeliveryService::updateStatus()
  5. Update webhook log status: processed (or failed on exception)
```

No authentication middleware on webhook routes — they are signed by the provider.

---

## 8. Admin UI Pages

| URL | Page | Purpose |
|-----|------|---------|
| `/delivery/providers` | Provider/Index | List all providers, set default, delete |
| `/delivery/providers/{id}/edit` | Provider/Edit | Edit credentials and config (key-value pairs) |
| `/delivery/settings` | Settings/Edit | Business address + default provider |
| `/deliveries` | Delivery/Index | Legacy delivery list with bulk actions |
| `/deliveries/create` | Delivery/Create | Create delivery with provider select |
| `/deliveries/{id}/edit` | Delivery/Edit | Edit delivery with provider select |

### Provider credentials and config

On the Provider Edit page, `credentials` and `config` are edited as **key-value pairs** — not raw JSON. Each driver reads them like:

```php
$this->provider->credentials['api_key']    // e.g. Pathao API key
$this->provider->config['flat_rate']       // e.g. ManualDriver flat rate
$this->provider->config['estimated_days']  // e.g. ManualDriver delivery estimate
```

---

## 9. Provider Select on Deliveries

The legacy `Delivery` model (deliveries table) has a `provider` string column. Its options are loaded **dynamically** from `config('delivery.drivers')`:

```php
// Delivery::providerOptions() returns:
[
    ['value' => 'pathao',    'label' => 'Pathao'],
    ['value' => 'redx',      'label' => 'Redx'],
    ['value' => 'steadfast', 'label' => 'Steadfast'],
    ['value' => 'sundarban', 'label' => 'Sundarban'],
    ['value' => 'manual',    'label' => 'Manual'],
]
```

Adding a new driver to `config/delivery.php` automatically makes it appear in the select dropdown — no frontend changes needed.

---

## 10. Key Environment Variables

```env
DELIVERY_DEFAULT_PROVIDER=manual   # fallback provider slug
DELIVERY_CACHE_TTL=6               # cost cache TTL in hours
```

---

## 11. Events & Listeners

| Event | Listener | Effect |
|-------|----------|--------|
| `DeliveryStatusUpdated` | `SendDeliveryStatusNotification` | Notify customer of status change |
| `DefaultProviderChanged` | `DispatchCostRecalculationJob` | Re-calculate cached costs when default provider changes |

`DefaultProviderChanged` fires from `DeliverySettingObserver` when `default_provider_id` changes on the settings record.

---

## 12. Running Migrations

```bash
# On all tenants
php artisan tenants:run "migrate --force"

# On a specific tenant
php artisan --tenant=demo migrate --force
```

Migration order (all in `DeliveryApp/Delivery/database/migrations/`):

1. `000001` — delivery_providers
2. `000002` — delivery_settings
3. `000003` — product_delivery_settings
4. `000004` — delivery_cost_cache
5. `000005` — delivery_orders (adds columns to orders table)
6. `000006` — delivery_status_logs
7. `000007` — delivery_webhook_logs
8. `000008` — add provider column to deliveries table
