PHP code example of centrex / laravel-inventory

1. Go to this page and download the library: Download centrex/laravel-inventory library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

centrex / laravel-inventory example snippets


use Centrex\Inventory\Facades\Inventory;

Inventory::seedPriceTiers();

// config/inventory.php

return [
    'base_currency'                    => env('INVENTORY_BASE_CURRENCY', 'BDT'),
    'drivers'                          => ['database' => ['connection' => env('INVENTORY_DB_CONNECTION')]],
    'table_prefix'                     => env('INVENTORY_TABLE_PREFIX', 'inv_'),
    'wac_precision'                    => 4,
    'exchange_rate_stale_days'         => 1,
    'price_not_found_throws'           => true,
    'qty_tolerance'                    => 0.0001,
    'default_shipping_rate_per_kg' => 0,
    'seed_price_tiers'                 => true,
];

use Centrex\Inventory\Facades\Inventory;

// 1 CNY = 16.50 BDT on 2026-04-10
Inventory::setExchangeRate('CNY', 16.50, '2026-04-10');

// 1 USD = 110.00 BDT (defaults to today)
Inventory::setExchangeRate('USD', 110.00);

// Get the rate for a currency on a specific date
$rate = Inventory::getExchangeRate('CNY', '2026-04-10'); // 16.50

// Convert between currencies
$bdt = Inventory::convertToBdt(100, 'CNY');   // 1650.0000 BDT
$usd = Inventory::convertFromBdt(1100, 'USD'); // 10.0000 USD

use Centrex\Inventory\Models\{Warehouse, Product, ProductCategory};

// Create warehouses
$wh_dhaka = Warehouse::create([
    'code'         => 'WH-BD-01',
    'name'         => 'Dhaka Warehouse',
    'country_code' => 'BD',
    'currency'     => 'BDT',  // native purchase/sale currency
    'is_default'   => true,
]);

$wh_china = Warehouse::create([
    'code'         => 'WH-CN-01',
    'name'         => 'Guangzhou Warehouse',
    'country_code' => 'CN',
    'currency'     => 'CNY',
]);

$wh_us = Warehouse::create([
    'code'         => 'WH-US-01',
    'name'         => 'New York Warehouse',
    'country_code' => 'US',
    'currency'     => 'USD',
]);

// Create a product with weight (

use Centrex\Inventory\Facades\Inventory;
use Centrex\Inventory\Enums\PriceTierCode;

// Global prices (apply to all warehouses unless overridden)
Inventory::setPrice($product->id, PriceTierCode::BASE->value,         5500.00);
Inventory::setPrice($product->id, PriceTierCode::WHOLESALE->value,    5200.00);
Inventory::setPrice($product->id, PriceTierCode::RETAIL->value,       6500.00);
Inventory::setPrice($product->id, PriceTierCode::DROPSHIPPING->value, 6200.00);
Inventory::setPrice($product->id, PriceTierCode::FCOM->value,         6800.00);

// Warehouse-specific override (China warehouse sells cheaper due to lower cost base)
Inventory::setPrice($product->id, PriceTierCode::WHOLESALE->value, 4800.00, $wh_china->id);
Inventory::setPrice($product->id, PriceTierCode::RETAIL->value,    5900.00, $wh_china->id);

// With local currency reference (informational — BDT is the source of truth)
Inventory::setPrice($product->id, PriceTierCode::RETAIL->value, 6500.00, $wh_dhaka->id, [
    'price_local' => 6500.00,
    'currency'    => 'BDT',
]);

// Time-limited price
Inventory::setPrice($product->id, PriceTierCode::RETAIL->value, 5500.00, null, [
    'effective_from' => '2026-06-01',
    'effective_to'   => '2026-06-30',
]);

// Resolve the effective retail price at Dhaka warehouse
// (warehouse-specific wins over global; falls back to global if no warehouse price)
$price = Inventory::resolvePrice($product->id, 'retail', $wh_dhaka->id);
echo $price->price_amount; // 6500.00

// Get the full price sheet for a product at a warehouse (all tiers)
$sheet = Inventory::getPriceSheet($product->id, $wh_china->id);
// Returns a Collection:
// [
//   ['tier_code' => 'base',         'price_amount' => 5500.00, 'source' => 'global'],
//   ['tier_code' => 'wholesale',    'price_amount' => 4800.00, 'source' => 'warehouse'],
//   ['tier_code' => 'retail',       'price_amount' => 5900.00, 'source' => 'warehouse'],
//   ['tier_code' => 'dropshipping', 'price_amount' => 6200.00, 'source' => 'global'],
//   ['tier_code' => 'fcom',         'price_amount' => 6800.00, 'source' => 'global'],
// ]

use Centrex\Inventory\Models\Coupon;

Coupon::create([
    'code'                    => 'SAVE10',
    'name'                    => 'Save 10%',
    'discount_type'           => 'percent', // percent|fixed
    'discount_value'          => 10,
    'minimum_subtotal_amount' => 1000,      // base currency amount
    'maximum_discount_amount' => 500,       // optional cap, base currency amount
    'usage_limit'             => 100,
    'starts_at'               => now(),
    'ends_at'                 => now()->addMonth(),
    'is_active'               => true,
]);

$saleOrder = Inventory::createSaleOrder([
    'warehouse_id'    => $warehouse->id,
    'customer_id'     => $customer->id,
    'currency'        => 'BDT',
    'price_tier_code' => 'b2c_retail',
    'coupon_code'     => 'SAVE10',
    'items'           => [[
        'product_id'       => $product->id,
        'qty_ordered'      => 2,
        'unit_price_local' => 500,
    ]],
]);

echo $saleOrder->coupon_discount_local; // 100.00
echo $saleOrder->total_local;           // 900.00

use Centrex\Inventory\Facades\Inventory;
use Centrex\Inventory\Models\Supplier;

$supplier = Supplier::create([
    'code'     => 'SUP-CN-001',
    'name'     => 'Shenzhen Electronics Co.',
    'currency' => 'CNY',
]);

// Purchase in CNY for the China warehouse
$po = Inventory::createPurchaseOrder([
    'warehouse_id'      => $wh_china->id,
    'supplier_id'       => $supplier->id,
    'currency'          => 'CNY',
    'exchange_rate' => 16.50,  // optional: auto-fetched from exchange_rates if omitted
    'tax_local'         => 100.00,
    'shipping_local'    => 50.00,
    'notes'             => 'Spring restock order',
    'items'             => [
        [
            'product_id'      => $product->id,
            'qty_ordered'     => 200,
            'unit_price_local' => 180.00,  // CNY per unit = 2,970 BDT at 16.50
        ],
    ],
]);

// $po->po_number  => "PO-20260410-0001"
// $po->total_amount  => (200 × 180 × 16.50) + tax_amount + shipping_amount

// Move through statuses
Inventory::submitPurchaseOrder($po->id);   // draft → submitted
Inventory::confirmPurchaseOrder($po->id);  // submitted → confirmed

// Create a draft GRN against the PO
$grn = Inventory::createStockReceipt($po->id, [
    [
        'purchase_order_item_id' => $po->items->first()->id,
        'qty_received'           => 200,
        // unit_cost_local defaults to PO unit_price_local if omitted
    ],
]);

// Post the GRN: increments inv_warehouse_products.qty_on_hand,
// recalculates WAC, writes a stock_movement row
$grn = Inventory::postStockReceipt($grn->id);

// Check new stock level
$stock = Inventory::getStockLevel($product->id, $wh_china->id);
echo $stock->qty_on_hand; // 200
echo $stock->wac_amount;     // 2970.0000 (180 CNY × 16.50)

// Void a posted GRN (writes compensating movement — never deletes)
Inventory::voidStockReceipt($grn->id);

use Centrex\Inventory\Facades\Inventory;
use Centrex\Inventory\Models\Customer;

$customer = Customer::create([
    'code'          => 'CUST-001',
    'name'          => 'Acme Traders',
    'currency'      => 'USD',
    'price_tier_id' => \Centrex\Inventory\Models\PriceTier::where('code', 'wholesale')->value('id'),
]);

// Sell in USD from the Dhaka warehouse at wholesale prices
$so = Inventory::createSaleOrder([
    'warehouse_id'      => $wh_dhaka->id,
    'customer_id'       => $customer->id,
    'price_tier_code'   => 'wholesale',
    'currency'          => 'USD',
    'exchange_rate' => 110.00,
    'items'             => [
        [
            'product_id'  => $product->id,
            'qty_ordered' => 50,
            // unit_price_local auto-resolved from inv_product_prices (wholesale tier, Dhaka warehouse)
            // override with 'unit_price_local' => 47.27 if needed
        ],
    ],
]);

// Confirm → reserve stock → fulfill
Inventory::confirmSaleOrder($so->id);
Inventory::reserveStock($so->id);         // increments qty_reserved, blocks overselling
Inventory::fulfillSaleOrder($so->id);     // decrements qty_on_hand, stamps COGS at WAC

$so->refresh();
echo $so->status->value;   // "fulfilled"
echo $so->cogs_amount;        // 50 × wac_amount at time of fulfillment
echo $so->grossMarginPct(); // gross margin %

// Partial fulfillment — supply a qty per line item
Inventory::fulfillSaleOrder($so->id, [
    $so->items->first()->id => 30,  // fulfill only 30 of 50 ordered
]);
// $so->status → "partial"

// Cancel (releases reserved qty automatically)
Inventory::cancelSaleOrder($so->id);

$so = Inventory::createSaleOrder([
    'warehouse_id'    => $wh_dhaka->id,
    'price_tier_code' => 'retail',       // order-level default
    'currency'        => 'BDT',
    'exchange_rate' => 1.0,
    'items'           => [
        [
            'product_id'       => $product->id,
            'qty_ordered'      => 10,
            'price_tier_code'  => 'fcom',  // override tier for this line
            'discount_pct'     => 5,        // 5% line discount
        ],
    ],
]);

use Centrex\Inventory\Facades\Inventory;

// Transfer 100 units from China → Dhaka at 15 BDT/kg shipping
$transfer = Inventory::createTransfer([
    'from_warehouse_id'        => $wh_china->id,
    'to_warehouse_id'          => $wh_dhaka->id,
    'shipping_rate_per_kg' => 15.00,
    'notes'                    => 'Monthly replenishment',
    'items'                    => [
        [
            'product_id' => $product->id,
            'qty_sent'   => 100,
            // weight_kg_total = 100 × 0.350 kg = 35 kg
        ],
    ],
]);

// $transfer->shipping_cost_amount          => 35 kg × 15 = 525.00 BDT
// $transfer->items[0]->shipping_allocated_amount => 525.00 (100% weight share)
// $transfer->items[0]->unit_landed_cost_amount   => source_wac + 525/100 = WAC + 5.25

// Dispatch: decrements China stock, increments China qty_in_transit
Inventory::dispatchTransfer($transfer->id);

// Receive at Dhaka: increments Dhaka stock, recalculates Dhaka WAC
// using unit_landed_cost_amount (source WAC + allocated shipping)
Inventory::receiveTransfer($transfer->id);

// Partial receipt — supply a qty per transfer item
Inventory::receiveTransfer($transfer->id, [
    $transfer->items->first()->id => 60,  // receive 60 of 100 sent
]);
// $transfer->status → "partial"

$transfer = Inventory::createTransfer([
    'from_warehouse_id'        => $wh_us->id,
    'to_warehouse_id'          => $wh_dhaka->id,
    'shipping_rate_per_kg' => 80.00,  // air freight
    'items'                    => [
        ['product_id' => $productA->id, 'qty_sent' => 20],  // 2 kg each → 40 kg
        ['product_id' => $productB->id, 'qty_sent' => 50],  // 0.1 kg each → 5 kg
        // total weight: 45 kg, shipping: 3,600 BDT
        // productA gets: 40/45 × 3600 = 3,200 BDT shipping
        // productB gets:  5/45 × 3600 =   400 BDT shipping
    ],
]);

use Centrex\Inventory\Facades\Inventory;
use Centrex\Inventory\Enums\AdjustmentReason;

// The system reads current qty_on_hand automatically — you only supply qty_actual
$adjustment = Inventory::createAdjustment([
    'warehouse_id' => $wh_dhaka->id,
    'reason'       => AdjustmentReason::CYCLE_COUNT->value,
    'notes'        => 'Monthly cycle count — bay 3',
    'items'        => [
        [
            'product_id' => $product->id,
            'qty_actual'  => 145,  // system shows 150, actual count is 145 → delta: -5
        ],
    ],
]);

// Post: applies qty_delta to warehouse stock, writes adjustment_out movement
Inventory::postAdjustment($adjustment->id);

// Write-off example
$adj = Inventory::createAdjustment([
    'warehouse_id' => $wh_dhaka->id,
    'reason'       => AdjustmentReason::DAMAGE->value,
    'items'        => [
        ['product_id' => $product->id, 'qty_actual' => 140],
    ],
]);
Inventory::postAdjustment($adj->id);

use Centrex\Inventory\Facades\Inventory;

// Single product at a warehouse
$stock = Inventory::getStockLevel($product->id, $wh_dhaka->id);
echo $stock->qty_on_hand;   // 145
echo $stock->qty_reserved;  // 50 (reserved by pending sale orders)
echo $stock->qty_in_transit; // 100 (dispatched transfer not yet received)
echo $stock->qtyAvailable(); // qty_on_hand - qty_reserved = 95
echo $stock->wac_amount;        // weighted average cost in BDT

// All products at a warehouse
$levels = Inventory::getStockLevels($wh_dhaka->id);

// Low stock alerts (all warehouses, or filter by one)
$lowStock = Inventory::getLowStockItems();
$lowStock = Inventory::getLowStockItems($wh_dhaka->id);

// Total stock value in BDT
$totalValue = Inventory::getStockValue();                // all warehouses
$warehouseValue = Inventory::getStockValue($wh_dhaka->id); // single warehouse

$report = Inventory::stockValuationReport($wh_dhaka->id);
// Returns a Collection of arrays:
// [
//   'warehouse'       => 'Dhaka Warehouse',
//   'sku'             => 'PHONE-X1',
//   'product'         => 'Smartphone X1',
//   'qty_on_hand'     => 145.0,
//   'qty_reserved'    => 50.0,
//   'qty_available'   => 95.0,
//   'wac_amount'         => 3105.2500,
//   'total_value_amount' => 450261.25,
// ]

$movements = Inventory::getMovementHistory(
    productId:   $product->id,
    warehouseId: $wh_dhaka->id,
    from:        '2026-01-01',
    to:          '2026-04-30',
);

foreach ($movements as $m) {
    echo "[{$m->moved_at}] {$m->movement_type->label()} {$m->direction} {$m->qty} "
       . "| before: {$m->qty_before} → after: {$m->qty_after} "
       . "| WAC: {$m->wac_amount} BDT\n";
}

use Centrex\Inventory\Facades\Inventory;

// Create a customer
$customer = Inventory::createCustomer([
    'code'                => 'CUST-001',
    'name'                => 'Rahman Brothers Ltd',
    'email'               => '[email protected]',
    'phone'               => '+880 1711-000000',
    'currency'            => 'BDT',
    'credit_limit_amount' => 500000,        // 0 = no limit
    'price_tier_code'     => 'b2b_wholesale',
    'is_active'           => true,
]);

Inventory::updateCustomer($customer->id, ['credit_limit_amount' => 750000]);
Inventory::deleteCustomer($customer->id);   // soft-delete

// Link customer to any Eloquent model (User, Contact, etc.)
$customer = Inventory::findCustomerForModel(App\Models\User::class, $userId);

// Credit limit snapshot — real-time exposure across open orders
$credit = Inventory::customerCreditSnapshot($customer->id);
// returns:
// [
//   'credit_limit_amount'   => 500000.0,
//   'outstanding_exposure'  => 123400.0,  // sum of open SO totals in base currency
//   'available_credit'      => 376600.0,
// ]

// Recent order history
$history = Inventory::customerHistory($customer->id, limit: 10);

$so = Inventory::createSaleOrder([
    'customer_id'     => $customer->id,
    'warehouse_id'    => $wh->id,
    'price_tier_code' => 'b2b_wholesale',
    'currency'        => 'BDT',
    'items'           => [...],
]);

if ($so->credit_override_

use Centrex\Inventory\Models\Supplier;

$supplier = Supplier::create([
    'code'          => 'SUP-CN-001',
    'name'          => 'Shenzhen Electronics Co.',
    'country_code'  => 'CN',
    'currency'      => 'CNY',
    'contact_name'  => 'Li Wei',
    'contact_email' => '[email protected]',
    'contact_phone' => '+86 ...',
    'is_active'     => true,
]);

use Centrex\Inventory\Facades\Inventory;

$return = Inventory::createSaleReturn([
    'sale_order_id' => $so->id,
    'warehouse_id'  => $wh_dhaka->id,
    'customer_id'   => $customer->id,
    'returned_at'   => today(),
    'notes'         => 'Screen defect — customer complaint',
    'created_by'    => auth()->id(),
    'items' => [
        [
            'sale_order_item_id' => $so->items->first()->id,
            'qty_returned'       => 2,
            'unit_price_amount'  => 6500.00,  // selling price at time of sale
            'unit_cost_amount'   => 3105.25,  // WAC at time of fulfillment
        ],
    ],
]);

// Post: restores qty_on_hand (CUSTOMER_RETURN movement), reverses COGS JE in accounting
Inventory::postSaleReturn($return->id);

$return = Inventory::createPurchaseReturn([
    'purchase_order_id' => $po->id,
    'warehouse_id'      => $wh_dhaka->id,
    'supplier_id'       => $supplier->id,
    'returned_at'       => today(),
    'notes'             => 'Wrong SKU delivered',
    'created_by'        => auth()->id(),
    'items' => [
        [
            'purchase_order_item_id' => $po->items->first()->id,
            'qty_returned'           => 5,
            'unit_cost_amount'       => 2970.00,  // cost at receipt
        ],
    ],
]);

// Post: decrements qty_on_hand (RETURN_TO_SUPPLIER movement), recalculates WAC
Inventory::postPurchaseReturn($return->id);

use Centrex\Inventory\Facades\Inventory;

$forecast = Inventory::salesForecast(
    lookbackDays:  90,   // historical window to analyse
    forecastDays:  90,   // future projection window
    productLimit:  50,   // top N products by volume
    customerLimit: 25,   // top N customers by revenue
);

// $forecast['window']
// ['history_start', 'history_end', 'lookback_days', 'forecast_days']

// $forecast['summary']
// [
//   'products_tracked'          => 47,
//   'products_at_risk'          => 6,     // forecasted stockout within window
//   'forecast_qty'              => 2840,
//   'forecast_revenue'          => 18460000.0,
//   'g_supply_qty'];  // qty already on confirmed POs
    echo $p['confidence'];          // high | medium | low
}

// $forecast['timeline'] — daily/weekly series for charts
// ['categories', 'series' => ['qty', 'revenue', 'cash_in', 'cash_out', 'net'], 'totals']

Gate::define('inventory-admin', fn ($user) => $user->hasRole(['admin', 'inventory-manager']));

// Or override individual abilities
Gate::define('inventory.sale-orders.approve-credit', fn ($user) => $user->hasRole('finance-manager'));
Gate::define('inventory.adjustments.post',           fn ($user) => $user->hasRole('warehouse-manager'));

use Centrex\Inventory\Exceptions\{InsufficientStockException, PriceNotFoundException};

try {
    Inventory::reserveStock($so->id);
} catch (InsufficientStockException $e) {
    // Notify user: $e->getMessage()
}

try {
    $price = Inventory::resolvePrice($product->id, 'fcom', $wh_china->id);
} catch (PriceNotFoundException $e) {
    // No fcom price configured for this product/warehouse
}
bash
php artisan vendor:publish --tag="inventory-config"
php artisan migrate