# DID Normalization

## What It Does

When you create an inbound route in the GUI, you enter the DID once — typically in national format like `0414501359`. The system automatically generates dialplan entries for **every format** a SIP provider might deliver that same number in.

This means the same inbound route works regardless of which provider sends the call, without any per-provider configuration.

## The Problem It Solves

Different SIP providers deliver the same phone number in different formats:

| Provider Type | What They Send | Example |
|---------------|---------------|---------|
| ECN (your current provider) | Last 4 digits | `1359` |
| Other SA SIP trunks | National format | `0414501359` |
| Microsoft Teams Direct Routing | E.164 with + | `+27414501359` |
| International SIP peering | E.164 without + | `27414501359` |

Before this feature, if you entered `0414501359` in the GUI, only calls arriving as `0414501359` would match. ECN sends `1359` — no match — call drops. You'd have to manually figure out what format each provider uses and enter the DID in that exact format.

## How It Works

### When You Save an Inbound Route

1. You enter the DID in the GUI (e.g., `0414501359`)
2. The `DidNormalizer` service generates all possible variants:
   - `0414501359` — national (this becomes the **canonical** entry with all the routing logic)
   - `+27414501359` — E.164 with plus
   - `27414501359` — E.164 without plus
   - `1359` — short form (last 4 digits, ECN convention)
3. The canonical entry gets the full dialplan (CNAM lookup, recording, routing)
4. Each variant gets a 2-line shortcut: `Goto trunks-in,0414501359,1`

### When a Call Arrives

No matter what format the provider sends:

```
ECN sends "1359"
  → matches variant entry for "1359"
  → Goto trunks-in,0414501359,1
  → runs CNAM lookup, recording, routes to destination

Teams sends "+27414501359"
  → matches variant entry for "+27414501359"
  → Goto trunks-in,0414501359,1
  → same handler, same destination

National trunk sends "0414501359"
  → matches canonical entry directly
  → runs CNAM lookup, recording, routes to destination
```

All three calls end up at the same destination. Zero configuration needed per provider.

### When You Delete an Inbound Route

All variants are cleaned up automatically. No stale entries left behind.

### When You Edit a DID

If you change the DID from `0414501359` to `0414501360`:
1. All old variants (`0414501359`, `+27414501359`, `27414501359`, `1359`) are deleted
2. New variants (`0414501360`, `+27414501360`, `27414501360`, `1360`) are created
3. Clean, no orphans.

## Configuration

### Default Settings

In `.env` (or `config/dialing.php`):

```env
DIALING_COUNTRY_CODE=27
DIALING_SHORT_DID_LENGTH=4
```

| Setting | Default | Description |
|---------|---------|-------------|
| `DIALING_COUNTRY_CODE` | `27` (South Africa) | The country dialling code without the `+`. Used to convert between national and E.164 formats. |
| `DIALING_SHORT_DID_LENGTH` | `4` | How many trailing digits the short-DID format uses. ECN uses 4. Some providers use 3 or 5. |

### For Different Countries

If you deploy this PBX for a UK customer:
```env
DIALING_COUNTRY_CODE=44
DIALING_SHORT_DID_LENGTH=4
```

Input `02071234567` would generate:
- `02071234567` (national)
- `+442071234567` (E.164 +)
- `442071234567` (E.164 no +)
- `4567` (short, last 4)

### Per-Trunk Overrides

If a specific trunk uses a different short-DID length, configure it in `config/dialing.php`:

```php
'trunk_overrides' => [
    'trunk-vodacom' => ['short_did_length' => 0],  // never sends short DIDs
    'trunk-3digit'  => ['short_did_length' => 3],  // uses 3-digit short
],
```

## What Formats Are Supported

### Input (what you type in the GUI)

| Format | Example | Result |
|--------|---------|--------|
| National (recommended) | `0414501359` | Generates all 4 variants |
| E.164 with + | `+27414501359` | Generates all 4 variants (same result) |
| E.164 without + | `27414501359` | Generates all 4 variants (same result) |
| Short DID only | `1359` | Only generates `1359` (can't reverse-engineer the full number) |
| Asterisk pattern | `_041450XXXX` | Kept as-is, no normalization (patterns are provider-specific) |
| International | `+1234567890` | Generates E.164 + and E.164 no-plus only (different country) |

### Best Practice

Always enter the **full national number** (e.g., `0414501359`). This gives maximum coverage across all provider formats.

If you only know the short DID (e.g., ECN told you "your DID is 1359"), enter `1359` — it will work for ECN, but won't match if you later switch to a provider that sends the full number. Better to find the full number and enter that.

## CLI Tool

For debugging or verification:

```bash
php artisan did:normalize 0414501359
```

Output:
```
+-------------+--------------+--------------+
| Role        | Pattern      | Type         |
+-------------+--------------+--------------+
| * canonical | 0414501359   | national     |
|   variant   | +27414501359 | e164_plus    |
|   variant   | 27414501359  | e164_nonplus |
|   variant   | 1359         | short_4      |
+-------------+--------------+--------------+
Canonical: 0414501359
```

With options:
```bash
php artisan did:normalize 02071234567 --cc=44 --short-len=4
```

## How It Looks in the Database

After saving an inbound route for DID `0414501359` with destination "Time Condition #5":

```sql
SELECT exten, priority, app, appdata 
FROM extensions 
WHERE context = 'trunks-in' 
AND exten IN ('0414501359', '+27414501359', '27414501359', '1359');
```

| exten | priority | app | appdata |
|-------|----------|-----|---------|
| 0414501359 | 1 | NoOp | INBOUND DID=0414501359 (variants: ...) FROM: ${CALLERID(all)} |
| 0414501359 | 2 | Set | CALLERID(name)=${CURL(...)} |
| 0414501359 | 3 | Goto | time-condition-5,s,1 |
| +27414501359 | 1 | NoOp | DID variant (e164_plus) → canonical 0414501359 |
| +27414501359 | 2 | Goto | trunks-in,0414501359,1 |
| 27414501359 | 1 | NoOp | DID variant (e164_nonplus) → canonical 0414501359 |
| 27414501359 | 2 | Goto | trunks-in,0414501359,1 |
| 1359 | 1 | NoOp | DID variant (short_4) → canonical 0414501359 |
| 1359 | 2 | Goto | trunks-in,0414501359,1 |

The canonical entry has all the logic (CNAM, recording, routing). The variants are just 2-line redirects.

## Troubleshooting

### "Call arrives but doesn't match any route"

1. Check what DID the provider actually sent:
   ```bash
   asterisk -rvvv
   # Make a test call, look for:
   # Executing [XXXX@from-ecn-in:1] Set("...", "__ROUTEDID=XXXX")
   ```
   The `XXXX` is what the provider sent.

2. Check if that variant exists in the dialplan:
   ```bash
   php artisan did:normalize XXXX
   ```

3. If the provider sends a format you didn't expect (e.g., 5 digits instead of 4), update `DIALING_SHORT_DID_LENGTH` in `.env` and re-save the inbound route.

### "I changed the DID but old calls still route to the old destination"

Re-save the inbound route in the GUI. The old variants are cleaned up and new ones generated on every save.

### "I have two DIDs that share the same short form"

Example: `0414501359` and `0824981359` both have short form `1359`. This is a collision. The last one saved wins. Solution: increase `DIALING_SHORT_DID_LENGTH` to 5 or 6 to avoid collisions, or use Asterisk patterns (`_XXXX`) for the ambiguous range.

## Files

| File | Purpose |
|------|---------|
| `app/Services/DidNormalizer.php` | Core normalization logic |
| `config/dialing.php` | Country code + short DID length config |
| `app/Console/Commands/TestDidNormalize.php` | CLI debugging tool |
| `app/Http/Controllers/Trunks/InboundRouteController.php` | Uses normalizer on save/delete |
