intelli-dust/vario-ng

VarioNG (VOLVO VIBE) REST API client for PHP - v3.0 with vibe.volvocars.biz support

Installs: 9

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 2

Forks: 0

Open Issues: 0

pkg:composer/intelli-dust/vario-ng

0.1.10 2022-03-28 11:22 UTC

This package is not auto-updated.

Last update: 2026-01-15 07:05:41 UTC


README

Rest Client for Volvo Cars VIBE (VarioNG) API - https://vibe.volvocars.biz/

⚠️ Breaking Changes in v3.0:

  • New base URL: vibe.volvocars.biz (old vario-ng.com deprecated as of Jan 15, 2026)
  • postStockOnHand() removed, replaced with postRetailerStockOnHand() and postImporterStockOnHand()
  • Response format changed to include details[] array
  • JWT token auto-refresh on expiration

Installation

Via Composer

composer require intelli-dust/vario-ng

Manual Installation

  1. Clone the repository:
git clone https://github.com/IntelliDust/VarioNG.git
  1. Install dependencies:
cd VarioNG
composer install
  1. Include in your project:
require_once 'vendor/autoload.php';

Configuration

Basic Configuration

$client = new VarioNGClient([
    'userName' => 'YOUR_USERNAME',
    'password' => 'YOUR_PASSWORD',
    'environment' => 'production'  // or 'qa' for testing
]);

Advanced Configuration

$client = new VarioNGClient([
    'userName' => 'YOUR_USERNAME',
    'password' => 'YOUR_PASSWORD',
    'base_url' => 'https://vibe.volvocars.biz/ws/dcs/',  // Custom URL
    'environment' => 'production',
    'user_agent' => 'MyApp/1.0',
    'jwt_timeout' => 120  // minutes
]);

Environment Options

  • production: https://vibe.volvocars.biz/ws/dcs/
  • qa: https://qa-vibe.volvocars.biz/ws/dcs/

Workflow (Pracovný postup)

1. Inicializácia a autentifikácia

<?php
require_once 'vendor/autoload.php';

// Krok 1: Vytvorenie klienta
$client = new VarioNGClient([
    'userName' => 'YOUR_USERNAME',
    'password' => 'YOUR_PASSWORD',
    'environment' => 'production'  // alebo 'qa' pre testovanie
]);

// Krok 2: Prihlásenie (získanie JWT tokenu)
try {
    $client->logIn();
    // Token je automaticky uložený a bude sa používať pri všetkých požiadavkách
    // Token je platný 120 minút a automaticky sa obnovuje pri expirácii
} catch (Exception $e) {
    die("Login failed: " . $e->getMessage());
}

2. Odoslanie dát (Create/Update)

Všeobecný workflow pre všetky endpoint-y:

// Krok 1: Pripravte dáta v správnej štruktúre
$data = [
    'authorizationID' => 'YOUR_CODE',  // Retailer alebo Importer kód
    // bodID sa vygeneruje automaticky, ak nie je poskytnutý
    // requestedBy sa nastaví automaticky podľa metódy
    // toDelete sa nastaví automaticky na 'N'

    // ... špecifické dáta pre každý endpoint
];

// Krok 2: Odošlite dáta
try {
    $response = $client->postJobCard($data);  // alebo iná metóda

    // Krok 3: Spracujte odpoveď
    if ($response->details[0]->rc == '1') {
        echo "Úspech! BodID: " . $response->bodID . "\n";
        echo "Správa: " . $response->details[0]->message . "\n";
    }
} catch (Exception $e) {
    echo "Chyba: " . $e->getMessage() . "\n";
}

3. Mazanie dát (Delete)

Delete operácie vyžadujú iba kľúčové polia:

// Pre Job Card: retailerCode, woNumber, woYear
$deleteData = [
    'authorizationID' => 'SK006',
    'workOrder' => [
        'retailerCode' => 'SK006',
        'woNumber' => '202600024',
        'woYear' => '2026'
    ]
];

try {
    // Druhý parameter 'Y' značí delete operáciu
    $response = $client->postJobCard($deleteData, 'Y');
    echo "Zmazané!\n";
} catch (Exception $e) {
    echo "Chyba pri mazaní: " . $e->getMessage() . "\n";
}

4. Kompletný workflow pre Job Card

<?php
require_once 'vendor/autoload.php';

// === INICIALIZÁCIA ===
$client = new VarioNGClient([
    'userName' => 'IMP02108',
    'password' => 'your_password',
    'environment' => 'production'
]);

try {
    // === AUTENTIFIKÁCIA ===
    $client->logIn();
    echo "✓ Prihlásený\n";

    // === PRÍPRAVA DÁT ===
    $jobCard = [
        'authorizationID' => 'SK006',
        'workOrder' => [
            'retailerCode' => 'SK006',
            'woNumber' => '202600024',
            'woYear' => '2026',
            'chassis' => 'TEST1234567890123',
            'regNo' => 'AA-123BB',
            'body' => 'S-90',
            'woOpenDate' => '2026-01-09',
            'woCloseDate' => '2026-01-09',
            'custCode' => 'TEST001',
            'custName' => 'Example Company s.r.o.',
            'custPhone' => '0900123456',
            'custEMail' => 'test@example.com'
        ],
        'wJobs' => [
            [
                'jobID' => '1',
                'consumerNotes' => 'Service work'
            ]
        ],
        'labor' => [
            [
                'seqNumber' => '1',
                'jobID' => '1',
                'operCode' => '17300-2',
                'operDesc' => 'Service after 239713 km',
                'qty' => 3.000,
                'listPrice' => 204.00,
                'netAmount' => 204.00,
                'curCode' => 'EUR',
                'technician' => 'Tech01'
            ]
        ],
        'parts' => [
            [
                'seqNumber' => '1',
                'jobID' => '1',
                'spCode' => '32140029',
                'spDesc' => 'Oil filter',
                'usedQty' => 1.000,
                'listPrice' => 20.14,
                'curCode' => 'EUR',
                'volvoOriginal' => 'Y'
            ]
        ]
    ];

    // === ODOSLANIE ===
    echo "Odosielam job card...\n";
    $response = $client->postJobCard($jobCard);

    // === SPRACOVANIE ODPOVEDE ===
    echo "✓ Job card odoslaný\n";
    echo "  BodID: {$response->bodID}\n";
    echo "  AuthorizationID: {$response->authorizationID}\n";
    echo "  RC: {$response->details[0]->rc}\n";
    echo "  Správa: {$response->details[0]->message}\n";

    // === MAZANIE (VOLITEĽNÉ) ===
    echo "\nMažem job card...\n";
    $response = $client->postJobCard($jobCard, 'Y');
    echo "✓ Job card zmazaný\n";

} catch (Exception $e) {
    // === ERROR HANDLING ===
    echo "❌ Chyba: " . $e->getMessage() . "\n";

    // Parsovanie error kódov
    if (strpos($e->getMessage(), 'RC:4403') !== false) {
        echo "  → Chýba povinné pole\n";
    } elseif (strpos($e->getMessage(), 'RC:164') !== false) {
        echo "  → JWT token expiroval, skúste znova\n";
    } elseif (strpos($e->getMessage(), 'RC:4411') !== false) {
        echo "  → Duplicitný záznam\n";
    }
}

5. Workflow pre Stock Inventory (Mesačný zber)

⚠️ Dôležité: Stock inventory sa odosiela raz za mesiac!

// === RETAILER STOCK (D1) ===
$retailerStock = [
    'authorizationID' => 'SK006',
    'stockInventory' => [
        'retailerCode' => 'SK006',
        'stockMonth' => date('Ym'),  // YYYYMM formát (napr. 202601)
        'stockInventoryDet' => [
            [
                'spCode' => '32140029',
                'spDesc' => 'Oil filter',
                'onHand' => 150,
                'allocated' => 25,
                'unitCost' => 18.50,
                'listPrice' => 22.00,
                'curCode' => 'EUR',
                'volvoOriginal' => 'Y'
            ]
            // ... ďalšie položky
        ]
    ]
];

try {
    $client->logIn();
    $response = $client->postRetailerStockOnHand($retailerStock);
    echo "✓ Retailer stock odoslaný pre mesiac: " . date('Ym') . "\n";

} catch (Exception $e) {
    if (strpos($e->getMessage(), 'RC:4411') !== false) {
        echo "Stock už bol odoslaný pre tento mesiac!\n";
    } else {
        echo "Chyba: " . $e->getMessage() . "\n";
    }
}

// === IMPORTER STOCK (D2) ===
$importerStock = [
    'authorizationID' => 'IMP021',
    'stockInventory' => [
        'importerCode' => 'IMP021',
        'stockMonth' => date('Ym'),
        'stockInventoryDet' => [
            // ... položky inventára
        ]
    ]
];

$response = $client->postImporterStockOnHand($importerStock);

6. Best Practices

6.1 Vždy testujte najprv na QA prostredí

// Pre testovanie
$client = new VarioNGClient([
    'userName' => 'test_user',
    'password' => 'test_pass',
    'environment' => 'qa'  // QA endpoint
]);

// Keď testy prejdú, prepnite na production
$client = new VarioNGClient([
    'userName' => 'prod_user',
    'password' => 'prod_pass',
    'environment' => 'production'
]);

6.2 Logujte všetky operácie

try {
    $response = $client->postJobCard($jobCard);

    // Logujte bodID pre tracovanie
    error_log("Job card sent: bodID={$response->bodID}, RC={$response->details[0]->rc}");

} catch (Exception $e) {
    // Logujte chyby
    error_log("Job card failed: " . $e->getMessage());
}

6.3 Validujte dáta pred odoslaním

// Kontrola povinných polí
if (empty($jobCard['workOrder']['retailerCode'])) {
    throw new Exception("retailerCode je povinný!");
}

if (empty($jobCard['workOrder']['woNumber'])) {
    throw new Exception("woNumber je povinný!");
}

// Kontrola formátu dátumov
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $jobCard['workOrder']['woOpenDate'])) {
    throw new Exception("Nesprávny formát dátumu! Použite YYYY-MM-DD");
}

6.4 Spracovávajte všetky chyby

try {
    $client->logIn();
    $response = $client->postJobCard($jobCard);

} catch (Exception $e) {
    $errorMsg = $e->getMessage();

    // Kontrola špecifických error kódov
    if (strpos($errorMsg, 'RC:-1') !== false) {
        // Autentifikácia zlyhala
        echo "Nesprávne prihlasovacie údaje\n";
    } elseif (strpos($errorMsg, 'RC:164') !== false) {
        // JWT expiroval - automaticky obnovený, opakujte operáciu
        echo "Token expiroval, opakujem...\n";
        $response = $client->postJobCard($jobCard);
    } elseif (strpos($errorMsg, 'RC:4403') !== false) {
        // Chýba povinné pole
        preg_match('/Field is: (\w+)/', $errorMsg, $matches);
        echo "Chýba povinné pole: " . $matches[1] . "\n";
    } elseif (strpos($errorMsg, 'RC:4417') !== false) {
        // Duplicitný part code
        echo "Duplicitný part code v zozname!\n";
    } else {
        // Iná chyba
        echo "Neočakávaná chyba: {$errorMsg}\n";
    }
}

6.5 Použite transakčný prístup

// Ak odosielate viacero položiek, sledujte úspešnosť
$results = [];

foreach ($jobCards as $jobCard) {
    try {
        $response = $client->postJobCard($jobCard);
        $results[] = [
            'wo' => $jobCard['workOrder']['woNumber'],
            'status' => 'success',
            'bodID' => $response->bodID
        ];
    } catch (Exception $e) {
        $results[] = [
            'wo' => $jobCard['workOrder']['woNumber'],
            'status' => 'failed',
            'error' => $e->getMessage()
        ];
    }
}

// Report
echo "Úspešných: " . count(array_filter($results, fn($r) => $r['status'] === 'success')) . "\n";
echo "Neúspešných: " . count(array_filter($results, fn($r) => $r['status'] === 'failed')) . "\n";

7. Časté scenáre

7.1 Denne: Odoslanie job cards

$client->logIn();

foreach ($dailyJobCards as $jobCard) {
    try {
        $response = $client->postJobCard($jobCard);
        echo "✓ WO {$jobCard['workOrder']['woNumber']} odoslaný\n";
    } catch (Exception $e) {
        echo "✗ WO {$jobCard['workOrder']['woNumber']} zlyhal: {$e->getMessage()}\n";
    }
}

7.2 Denne: Odoslanie sales parts

$client->logIn();

foreach ($salesInvoices as $invoice) {
    $response = $client->postSalesPart($invoice);
}

7.3 Mesačne: Stock inventory (prvý deň mesiaca)

if (date('d') == '01') {
    $client->logIn();

    // Retailer stock
    $response = $client->postRetailerStockOnHand($retailerStock);

    // Importer stock (ak máte centrálny sklad)
    $response = $client->postImporterStockOnHand($importerStock);
}

7.4 Pri predaji vozidla: Car sales + Lead

$client->logIn();

// Najprv odošlite lead (ak ešte nebol odoslaný)
$response = $client->postLead($leadData);

// Potom car sales
$response = $client->postSalesCar($carSalesData);

8. Debugging

8.1 Zapnite error reporting

error_reporting(E_ALL);
ini_set('display_errors', 1);

8.2 Logujte raw responses

// Dočasne upravte parseResponse() pre debugging
protected function parseResponse($response, $operationName) {
    error_log("Raw response for {$operationName}: " . $response->response);

    // ... zvyšok kódu
}

8.3 Validujte JSON

$json = json_encode($jobCard);
if (json_last_error() !== JSON_ERROR_NONE) {
    die("Invalid JSON: " . json_last_error_msg());
}

Usage Examples

1. Authentication

try {
    $client->logIn();
    echo "Login successful!\n";
} catch (Exception $e) {
    echo "Login failed: " . $e->getMessage() . "\n";
}

2. Post Job Card (Service Work Order)

$jobCard = [
    'authorizationID' => 'RET001',
    'workOrder' => [
        'retailerCode' => 'RET001',
        'woNumber' => 'WO123456',
        'woYear' => '2026',
        'chassis' => '1HGBH41JXMN109186',
        'regNo' => 'ABC123',
        'woOpenDate' => '2026-01-10',
        'woCloseDate' => '2026-01-13',
        'custCode' => 'CUST001',
        'custName' => 'John Doe',
        'custPhone' => '421901234567',
        'custEMail' => 'john@example.com'
    ],
    'wJobs' => [
        [
            'laborCode' => 'LAB001',
            'laborDesc' => 'Oil change',
            'technicianID' => 'TECH01',
            'laborHours' => 1.5
        ]
    ],
    'parts' => [
        [
            'spCode' => 'PART001',
            'spDesc' => 'Engine oil 5W30',
            'quantity' => 5,
            'unitPrice' => 8.50
        ]
    ]
];

try {
    $response = $client->postJobCard($jobCard);
    echo "Job card posted successfully! BodID: " . $response->bodID . "\n";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

3. Post Sales Part Invoice

$salesPart = [
    'authorizationID' => 'RET001',
    'salesinvoice' => [
        'retailerCode' => 'RET001',
        'invNo' => 'INV789',
        'invDate' => '2026-01-13',
        'custCode' => 'CUST002',
        'custName' => 'Jane Smith',
        'invLineItems' => [
            [
                'spCode' => 'PART002',
                'spDesc' => 'Brake pads',
                'quantity' => 4,
                'unitPrice' => 45.00,
                'listPrice' => 50.00
            ]
        ]
    ]
];

try {
    $response = $client->postSalesPart($salesPart);
    echo "Sales part posted! BodID: " . $response->bodID . "\n";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

4. Post Retailer Stock Inventory

$retailerStock = [
    'authorizationID' => 'RET001',
    'stockInventory' => [
        'retailerCode' => 'RET001',
        'stockMonth' => '202601',  // YYYYMM
        'stockInventoryDet' => [
            [
                'spCode' => 'PART001',
                'spDesc' => 'Engine oil 5W30',
                'onHand' => 100,
                'allocated' => 20,
                'unitCost' => 7.50,
                'listPrice' => 8.50,
                'curCode' => 'EUR',
                'volvoOriginal' => 'Y'
            ]
        ]
    ]
];

try {
    $response = $client->postRetailerStockOnHand($retailerStock);
    echo "Retailer stock posted!\n";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

5. Post Importer Stock Inventory

$importerStock = [
    'authorizationID' => 'IMP001',
    'stockInventory' => [
        'importerCode' => 'IMP001',
        'stockMonth' => '202601',
        'stockInventoryDet' => [
            [
                'spCode' => 'PART003',
                'spDesc' => 'Air filter',
                'onHand' => 500,
                'allocated' => 50,
                'unitCost' => 12.00,
                'listPrice' => 15.00,
                'curCode' => 'EUR',
                'volvoOriginal' => 'Y'
            ]
        ]
    ]
];

try {
    $response = $client->postImporterStockOnHand($importerStock);
    echo "Importer stock posted!\n";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

6. Post Car Sales

$carSales = [
    'authorizationID' => 'RET001',
    'vehsales' => [
        'retailerCode' => 'RET001',
        'chassis' => '1HGBH41JXMN109186',
        'invNo' => 'CARINV001',
        'invDate' => '2026-01-13',
        'custCode' => 'CUST003',
        'custName' => 'Bob Johnson',
        'custPhone' => '421901234567',
        'custEMail' => 'bob@example.com',
        'listPrice' => 45000.00,
        'discountTotal' => 2000.00,
        'netPrice' => 43000.00,
        'taxTotal' => 8600.00,
        'transactionPrice' => 51600.00,
        'salesAdvName' => 'Sales Advisor 1',
        'deliveryDate' => '2026-01-20'
    ]
];

try {
    $response = $client->postSalesCar($carSales);
    echo "Car sale posted!\n";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

7. Post Lead (with Test Drive and Offer)

$lead = [
    'authorizationID' => 'RET001',
    'lead' => [
        'leadID' => 'LEAD001',
        'retailerCode' => 'RET001',
        'leadDate' => '2026-01-10',
        'leadSource' => '05',  // Online source
        'custCode' => 'CUST004',
        'custName' => 'Alice Brown',
        'custPhone' => '421901234567',
        'custEMail' => 'alice@example.com',
        'body' => 'XC90',
        'testDrive' => [
            [
                'testDriveDate' => '2026-01-12',
                'testDriveResult' => 'POSITIVE'
            ]
        ],
        'offer' => [
            [
                'offerDate' => '2026-01-13',
                'offerAmount' => 52000.00,
                'offerStatus' => 'PENDING'
            ]
        ]
    ]
];

try {
    $response = $client->postLead($lead);
    echo "Lead posted!\n";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

8. Delete Operations

All methods support deletion via toDelete parameter:

// Delete a job card (only key fields required)
$jobCardDelete = [
    'authorizationID' => 'RET001',
    'workOrder' => [
        'retailerCode' => 'RET001',
        'woNumber' => 'WO123456',
        'woYear' => '2026'
    ]
];

try {
    $response = $client->postJobCard($jobCardDelete, 'Y');  // toDelete = 'Y'
    echo "Job card deleted!\n";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

Error Handling

Response Codes

RC Meaning Action
1 Success Operation completed
-1 Authorization failed Check credentials
164 JWT expired Auto-refreshed by client
4403 Mandatory field empty Check required fields
4410 Invalid field value Validate data
4411 Duplicate submission Already submitted this month
4417 Duplicate part code Check for duplicates

Example Error Handling

try {
    $client->logIn();
    $response = $client->postJobCard($jobCard);

    // Success
    echo "BodID: " . $response->bodID . "\n";
    echo "Message: " . $response->details[0]->message . "\n";

} catch (Exception $e) {
    // Parse error message
    if (strpos($e->getMessage(), 'RC:4403') !== false) {
        echo "Missing mandatory field!\n";
    } elseif (strpos($e->getMessage(), 'RC:164') !== false) {
        echo "Token expired, retrying...\n";
        // Auto-handled, retry operation
    } else {
        echo "Error: " . $e->getMessage() . "\n";
    }
}

Migration from v1.0

Breaking Changes

1. URL Change (Required!)

// OLD (v1.0):
'base_url' => 'https://www.vario-ng.com/vci/vciws/postVNG/'

// NEW (v3.0):
'environment' => 'production'  // Auto-selects vibe.volvocars.biz

2. Stock Inventory Split (Required!)

// OLD (v1.0):
$client->postStockOnHand($data);  // REMOVED!

// NEW (v3.0):
$client->postRetailerStockOnHand($data);  // For retailer inventory
// OR
$client->postImporterStockOnHand($data);  // For importer inventory

3. Response Format

// v3.0 responses include details array:
$response->bodID
$response->authorizationID
$response->details[0]->rc
$response->details[0]->message

Migration Checklist

  • Update base_url or set environment parameter
  • Replace postStockOnHand() calls with appropriate method
  • Update response parsing if accessing raw responses
  • Test on QA environment first
  • Deploy before January 15, 2026

API Methods

Method Description Category
logIn() Authenticate and get JWT token Auth
postJobCard($data, $toDelete) Submit service work order A&B
postSalesPart($data, $toDelete) Submit parts sales invoice F
postRetailerStockOnHand($data) Submit retailer inventory D1
postImporterStockOnHand($data) Submit importer inventory D2
postSalesCar($data, $toDelete) Submit car sales invoice H
postLead($data, $toDelete) Submit lead/test drive/offer J/K/L

Testing

Run tests:

composer test

Or manually test endpoints in tests/ directory:

export VIBE_USERNAME="your_username"
export VIBE_PASSWORD="your_password"
php tests/test_login.php
php tests/test_all_endpoints.php

See tests/README.md for detailed testing instructions.

Requirements

  • PHP >= 5.4.0
  • ext-curl
  • ext-json
  • tcdent/php-restclient >= 0.1.7

License

GPL-3.0-only

Support

Issues: https://github.com/IntelliDust/VarioNG/issues

Documentation

  • docs/ - Complete API specifications
  • TODO/ - Migration plans and endpoint mapping
  • tests/ - Usage examples and test suite

Credits

Changelog

v3.0.0 (2026-01-13)

  • BREAKING: New base URL vibe.volvocars.biz (old domain deprecated)
  • BREAKING: postStockOnHand() removed, split into postRetailerStockOnHand() and postImporterStockOnHand()
  • BREAKING: Response format changed to include details[] array
  • Added JWT token auto-refresh on expiration
  • Added postSalesCar() for vehicle sales tracking
  • Added postLead() for lead management
  • Added delete support via toDelete parameter
  • Added environment switching (production/QA)
  • Added UUID auto-generation for bodID
  • Enhanced error handling with specific RC codes

v0.1.10 (Previous)

  • Legacy v-1.0 implementation