# NGauge Delivery – GForce

Yii2 module integrating the [GForce](https://www.gforce.com) pallet network REST API into the NGauge platform.

## Requirements

- PHP 8.0+
- Yii2 ~2.0
- yii2-bootstrap4 ^2.0
- guzzlehttp/guzzle ^7.0
- A GForce API key (contact your GForce account manager)

## Installation

Add the module via Composer:

```bash
composer require portalpeople/ngauge-delivery-gforce
```

To build a multi-provider delivery stack, add optional delivery packages as needed:

```bash
composer require portalpeople/ngauge-delivery portalpeople/ngauge-delivery-gforce
# Optional provider modules
# composer require portalpeople/ngauge-delivery-palletline
# composer require portalpeople/ngauge-delivery-palletforce
# composer require portalpeople/ngauge-delivery-palletways
# composer require portalpeople/ngauge-delivery-upn
```

No manual module registration is required for normal Composer installs.
This package exposes `extra.bootstrap` in `composer.json`, and the module bootstrap
registers the `gforce` module at runtime when missing.

Manual config is only needed for non-standard bootstraps or local overrides.

If you still want to register it explicitly, use:

```php
// common/config/main.php
'modules' => [
    'gforce' => [
        'class' => \PortalPeople\gforce\Module::class,
    ],
],
```

And optionally:

```php
// common/config/main.php
'bootstrap' => ['gforce'],
```

## Configuration

Navigate to `/gforce/config` in your application to configure:

| Setting       | Description                                                         |
|---------------|---------------------------------------------------------------------|
| Enabled       | Enable or disable the module                                        |
| API URL       | Default: `https://api-uat.vigocloud.com/api/v1`                 |
| API Key       | Your GForce API key (stored encrypted)                          |
| Auto-Confirm  | If enabled, consignments are auto-confirmed to the depot on submit. If disabled, they are held on the customer portal for manual review. **Cannot be undone once confirmed.** |
| Timeout       | HTTP request timeout in seconds (default: 30)                       |
| Debug         | Log all API requests/responses to Yii application log               |

Alternatively, configure directly in `params.php`:

```php
// common/config/params.php
'plugins.gforce' => [
    'enabled'     => true,
    'apiUrl'      => 'https://api-uat.vigocloud.com/api/v1',
    'apiKey'      => '<encrypted-value>',
    'autoConfirm' => false,
    'timeout'     => 30,
    'debug'       => false,
],
```

## Stock Adjustment Events

The module listens for two application events and updates stock through the shared delivery calculator:

- Order transitions to `Paid` trigger a stock decrease for each cartline on the order.
- Cartline haulier changes trigger a stock rebalance so the previous haulier is restored and the new haulier is reduced.

Stock adjustment state is persisted to each cartline's `data` payload under `stock_adjustments` so repeated saves remain idempotent.

The GForce module now resolves stock adjustment helpers through `PortalPeople\\delivery\\...`
with backward compatibility to the existing shared implementation.

## Rate Sheet Import

You can import GForce rate-sheet files from:

- `/gforce/rate-import/index`

The importer accepts `.xlsx` and `.xls` files and now shows an import preview before saving.
The preview displays:

- Source rows detected in the spreadsheet
- Expanded postcode record count
- How many rows will be created vs updated
- A sample of records that will be written

After reviewing the sample screen, click **Confirm & Import** to persist changes.

The importer creates one `Shippingcosts` record per expanded postcode.
It does not consolidate postcode ranges into wildcard patterns.

## Usage

### Creating a consignment

```php
$gforce = Yii::$app->getModule('gforce');

$form = new \PortalPeople\gforce\models\ConsignmentForm();
$form->consignmentNumber     = 'MY-ORDER-001';
$form->consignmentType       = 'D';
$form->lifts                 = 1;
$form->weight                = 450;
$form->billUnitType          = 'HP';
$form->billUnitAmount        = 1;
$form->serviceCode           = 'A';
$form->deliveryContactName   = 'John Smith';
$form->deliveryTelephone     = '01234 567890';
$form->deliveryCompanyName   = 'ACME Ltd';
$form->deliveryLine          = '10 High Street';
$form->deliveryTown          = 'Bristol';
$form->deliveryCounty        = 'Bristol';
$form->deliveryPostCode      = 'BS1 1AA';
$form->deliveryCountry       = 'GB';

$manifest = $form->getManifest($gforce->autoConfirm);
$result   = Yii::$app->gforceClient->createConsignment($manifest, true);

$responseId = $result['Response']['Detail']['ImportDetail']['ResponseID'] ?? null;
```

### Getting labels

```php
$pdf = Yii::$app->gforceClient->getLabelsById($responseId);
// stream to browser or save to disk
```

### Checking consignment status

```php
$status = Yii::$app->gforceClient->getConStatusById($responseId);
```

## API Concepts

### `commit` parameter

- `commit=false` — validates the consignment data only, no data is stored. Useful for pre-submission validation.
- `commit=true` — stores the consignment. Combined with `Confirm`:
  - `Confirm=no` — consignment is held on the customer portal at [portal.gforce.com](https://portal.gforce.com) and must be manually manifested.
  - `Confirm=yes` — consignment is **immediately and irrevocably** transmitted to the depot system.

### Pallet types

| Code | Description          | Dimensions (L×W×H)    | Max Weight |
|------|----------------------|-----------------------|------------|
| FP   | Full Pallet          | 1.2×1.2×2.2m          | 1200 kg    |
| LP   | Light Pallet         | 1.2×1.2×2.2m          | 750 kg     |
| HP   | Half Pallet          | 1.2×1.2×1.1m          | 500 kg     |
| QP   | Quarter Pallet       | 1.2×1.2×0.8m          | 250 kg     |
| MQP  | Mini Quarter Pallet  | 1.2×1.2×0.6m          | 150 kg     |
| EPL  | Euro Pallet          | 0.8×1.2×2.2m          | 500 kg     |
| XLP  | XL Pallet            | 1.6×1.2×2.2m          | 1200 kg    |

### Notification System Groups

| Code | Event                                      |
|------|--------------------------------------------|
| 1    | Scanned out of sortation facility          |
| 2    | Scanned in to sortation facility           |
| 3    | Consignment out for delivery               |
| 4    | Delivery completed (POD available)         |
| 5    | ETA details (also triggers SMS)            |
| 6    | Image available (POD with signature)       |
| 7    | Scheduled AM delivery                      |
| 8    | Scheduled PM delivery                      |

## Available Endpoints (GForceClient)

| Method                                       | API Endpoint                              |
|----------------------------------------------|-------------------------------------------|
| `createConsignment(array, bool)`             | POST `/consignments?code={apiKey}`        |
| `updateConsignment(string, array)`           | PUT `/consignments/{trackingcode}?code={apiKey}` |
| `getConStatusById(string)`                   | GET `/consignments/status?trackingCode=...` |
| `getConStatusByConNo(string)`                | GET `/consignments/status?consignmentNumber=...` |
| `getConStatusByTrackingId(string)`           | GET `/consignments/status?trackingCode=...` |
| `getConsEntered(string $date)`               | GET `/consignments/status?from=...&to=...` |
| `getConsignment(string $trackingCode)`       | GET `/consignments/{trackingcode}?code={apiKey}` |
| `deleteConsignment(string $trackingCode)`    | DELETE `/consignments/{trackingcode}?code={apiKey}` |
| `getTrackingNotesByConNo(string)`            | GET `/consignments/status?consignmentNumber=...` |
| `getTrackingNotes(string $trackingCode)`     | GET `/consignments/status?trackingCode=...` |
| `getLabelsById(string $trackingCode)`        | GET `/consignments/{trackingcode}/labels?code={apiKey}` |
| `getLabelsByConNo(string $conNo)`            | Resolve tracking code then call labels endpoint |
| `getPodByTrackingId(string)`                 | GET `/consignments/images?...&includePod=true` then fetch image data |
| `testConnection()`                           | GET `/consignments/configurationsettings?code={apiKey}` |

## Changelog

See [CHANGELOG.md](CHANGELOG.md).
