PHPackages                             nomandev/noman-inventory - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. nomandev/noman-inventory

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

nomandev/noman-inventory
========================

A production-grade, universal inventory management package for Laravel. Supports multi-tenant, multi-warehouse, batches, serials, expiry, FEFO/FIFO, valuation, reservations, stock counts, and full audit trails.

1.0.5(3mo ago)0172MITPHPPHP ^8.2

Since Mar 13Pushed 3mo agoCompare

[ Source](https://github.com/nomanbhuiyan53/noman-inventory)[ Packagist](https://packagist.org/packages/nomandev/noman-inventory)[ RSS](/packages/nomandev-noman-inventory/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (6)Versions (7)Used By (0)

noman-inventory
===============

[](#noman-inventory)

**Package:** [`nomandev/noman-inventory`](https://packagist.org/packages/nomandev/noman-inventory) · **Namespace:** `Noman\Inventory`

A **production-grade, universal inventory management package** for Laravel 11 and 12.

Built for multi-tenant, multi-warehouse environments across diverse business domains: cow farms, pharmaceutical distributors, pet shops, clinics, warehouses, general retail, and more.

---

Features
--------

[](#features)

FeatureDetails**Append-only ledger**All stock changes recorded as immutable movement rows. Never destructive.**Documents**GRNs, Delivery Orders, Transfer Orders, Adjustments, Stock Counts, Reversals**Batches / Lots**Full batch tracking with expiry date and FEFO/FIFO allocation**Serial Numbers**Unit-level serial tracking for equipment and high-value items**Valuation**FIFO, Weighted Average, Standard Cost**Reservations**Soft-lock stock before issue; automatic expiry; reference linking**Stock Counts**Full count session workflow with variance calculation and auto-adjustment**Reversals**Compensating entries; original documents never modified**Projections**Denormalised balance + snapshot tables for fast reporting**Multi-warehouse**Hierarchical: Warehouse → Zone → Aisle → Rack → Shelf → Bin**Multi-tenant**Pluggable `TenantResolverContract`; no tenancy package hard-coded**Industry Profiles**Standard Goods, Pharma, Livestock Supply, Serialised Equipment, Pet Food**Policy Engine**Global → item-type → item level policy overrides**REST API**Full CRUD + all stock operations + reports**Blade UI**Optional web UI: dashboard, items, warehouses, stock ops, documents, reports**Events**12 domain events; listeners for balance projection---

Requirements
------------

[](#requirements)

- PHP **8.2+**
- Laravel **11.x** or **12.x**
- `spatie/laravel-package-tools` ^1.16

---

Installation
------------

[](#installation)

```
composer require nomandev/noman-inventory
```

The package auto-discovers via Laravel's package discovery mechanism.

### Use local package (for development or when publishing fails)

[](#use-local-package-for-development-or-when-publishing-fails)

If you develop the package locally or Packagist has an older version, add a path repository in your app's `composer.json`:

```
"repositories": [
    {
        "type": "path",
        "url": "../inventoryPackage/packages/noman-inventory",
        "options": { "symlink": true }
    }
],
```

Adjust `../inventoryPackage/...` so it points from your app directory to the package. Then:

```
rm -rf vendor/nomandev/noman-inventory
composer update nomandev/noman-inventory
```

### Installing from a local path (development)

[](#installing-from-a-local-path-development)

If you develop the package locally (e.g. in a monorepo) and get a `Failed to open stream` error for `routes/api.php`, use a path repository so your app uses the local package:

1. In your **Laravel app** `composer.json`, add:

```
{
    "repositories": [
        {
            "type": "path",
            "url": "../inventoryPackage/packages/noman-inventory"
        }
    ]
}
```

Adjust the `url` path if your layout differs (e.g. `../../inventoryPackage/packages/noman-inventory`).

2. Remove the old package and reinstall:

```
rm -rf vendor/nomandev/noman-inventory
composer update nomandev/noman-inventory
```

---

Publishing Config &amp; Migrations
----------------------------------

[](#publishing-config--migrations)

```
# Publish the configuration file
php artisan vendor:publish --tag="noman-inventory-config"

# Publish migrations (then review before running)
php artisan vendor:publish --tag="noman-inventory-migrations"

# Run migrations
php artisan migrate
```

---

Configuration Overview
----------------------

[](#configuration-overview)

```
// config/inventory.php

return [
    'tenant_mode'            => null,           // null = single-tenant
    'allow_negative_stock'   => false,
    'allocation_strategy'    => 'fefo',         // fefo | fifo | manual
    'valuation_method'       => 'weighted_average', // weighted_average | fifo | standard_cost
    'reservations_enabled'   => true,
    'reservation_expiry_minutes' => null,
    'batching_enabled'       => true,
    'expiry_alert_days'      => 30,
    'multi_warehouse'        => true,
    'bin_tracking'           => false,
    'routes_enabled'         => true,
    'api_middleware'         => ['api'],
    'route_prefix'           => 'inventory',
    'approval_required_for'  => [],            // ['adjustment', 'transfer']
    'currency'               => 'USD',
    'default_industry_profile' => 'standard_goods',
    'queue_projections'      => false,
    'tables'                 => [...],          // Override table names
    'bindings'               => [...],          // Override service implementations
];
```

---

Basic Setup
-----------

[](#basic-setup)

### 1. Create warehouses and items

[](#1-create-warehouses-and-items)

```
use Noman\Inventory\Infrastructure\Persistence\Eloquent\InventoryWarehouse;
use Noman\Inventory\Infrastructure\Persistence\Eloquent\InventoryItem;

$warehouse = InventoryWarehouse::create([
    'id'        => (string) \Illuminate\Support\Str::ulid(),
    'name'      => 'Main Warehouse',
    'code'      => 'WH-MAIN',
    'is_active' => true,
]);

$item = InventoryItem::create([
    'id'          => (string) \Illuminate\Support\Str::ulid(),
    'name'        => 'Paracetamol 500mg',
    'code'        => 'PARA-500',
    'sku'         => 'SKU-PARA-500',
    'is_active'   => true,
    'is_stockable'=> true,
    'industry_profile' => 'pharma_goods',
]);
```

### 2. Receive stock

[](#2-receive-stock)

```
use Noman\Inventory\Support\Facades\NomanInventoryFacade as NomanInventory;
use Noman\Inventory\Application\DTOs\ReceiveStockDTO;
use Noman\Inventory\Domain\Shared\ValueObjects\{Quantity, Money};

$result = NomanInventory::receive(new ReceiveStockDTO(
    itemId:      $item->id,
    quantity:    Quantity::of(500),
    warehouseId: $warehouse->id,
    unitCost:    Money::of(0.25, 'USD'),
    batchCode:   'BATCH-2024-001',
    expiryDate:  '2026-06-30',
    referenceDocNumber: 'PO-2024-00123',
));

echo $result->documentNumber;  // GRN-20241201-A3F2B1
echo $result->status->label(); // Posted
```

### 3. Issue stock

[](#3-issue-stock)

```
use Noman\Inventory\Application\DTOs\IssueStockDTO;
use Noman\Inventory\Domain\Shared\Enums\MovementType;

$result = NomanInventory::issue(new IssueStockDTO(
    itemId:        $item->id,
    quantity:      Quantity::of(50),
    warehouseId:   $warehouse->id,
    movementType:  MovementType::SaleOut,
    referenceDocNumber: 'SO-2024-00456',
));
```

### 4. Transfer stock between warehouses

[](#4-transfer-stock-between-warehouses)

```
use Noman\Inventory\Application\DTOs\TransferStockDTO;

$result = NomanInventory::transfer(new TransferStockDTO(
    itemId:          $item->id,
    quantity:        Quantity::of(100),
    fromWarehouseId: $warehouseA->id,
    toWarehouseId:   $warehouseB->id,
));
```

### 5. Reserve stock

[](#5-reserve-stock)

```
use Noman\Inventory\Application\DTOs\ReserveStockDTO;

$reservationId = NomanInventory::reserve(new ReserveStockDTO(
    itemId:        $item->id,
    quantity:      Quantity::of(20),
    warehouseId:   $warehouse->id,
    referenceType: 'sales_order',
    referenceId:   'SO-2024-00789',
    expiryMinutes: 60,
));

// Later, release it:
NomanInventory::releaseReservation($reservationId);
```

### 6. Adjust stock

[](#6-adjust-stock)

```
use Noman\Inventory\Application\DTOs\AdjustStockDTO;

// Positive = stock found; negative = stock lost
$result = NomanInventory::adjust(new AdjustStockDTO(
    itemId:      $item->id,
    quantity:    Quantity::of(-5),   // 5 units short
    warehouseId: $warehouse->id,
    reason:      'Damage found during count',
));
```

### 7. Reverse a document

[](#7-reverse-a-document)

```
use Noman\Inventory\Application\DTOs\ReverseDocumentDTO;

$reversal = NomanInventory::reverseDocument(new ReverseDocumentDTO(
    documentId: $result->documentId,
    reason:     'Incorrect item received — returning to vendor',
));
```

### 8. Check balance

[](#8-check-balance)

```
$available = NomanInventory::getBalance($item->id, $warehouse->id);
echo $available; // 445
```

---

Stock Count Workflow
--------------------

[](#stock-count-workflow)

```
use Noman\Inventory\Application\Actions\StartStockCountAction;
use Noman\Inventory\Application\Actions\CompleteStockCountAction;

// 1. Start the count
$session = app(StartStockCountAction::class)->execute(
    warehouseId: $warehouse->id,
    countDate:   '2024-12-01',
);

// 2. Complete with actual counted quantities
app(CompleteStockCountAction::class)->execute(
    sessionId:  $session->id,
    counts: [
        ['entry_id' => $entryId, 'counted_quantity' => 98],
    ],
    autoAdjust: true,   // posts adjustment docs for all variances
);
```

---

Tenancy Integration
-------------------

[](#tenancy-integration)

The package is fully multi-tenant ready but ships with a no-op `NullTenantResolver`. To enable tenancy, bind your own implementation:

```
// In your AppServiceProvider:
use Noman\Inventory\Contracts\TenantResolverContract;
use Noman\Inventory\Domain\Shared\ValueObjects\TenantId;

$this->app->bind(TenantResolverContract::class, function () {
    return new class implements TenantResolverContract {
        public function getCurrentTenantId(): ?TenantId
        {
            $tenantId = app('current_tenant')?->id; // your tenancy hook
            return $tenantId ? TenantId::of($tenantId) : null;
        }

        public function hasTenant(): bool
        {
            return app('current_tenant') !== null;
        }
    };
});
```

Also set in `.env`:

```
INVENTORY_TENANT_MODE=column
```

---

Industry Profiles
-----------------

[](#industry-profiles)

Assign a profile to item types or items to automatically apply the right policy defaults:

ProfileBatchExpirySerialLocationAllocation`standard_goods`❌❌❌❌FIFO`pharma_goods`✅✅❌✅FEFO`livestock_supply`✅✅❌❌FEFO`serialized_equipment`❌❌✅✅Manual`perishable_pet_food`✅✅❌❌FEFO---

Extension Points
----------------

[](#extension-points)

### Override any service implementation

[](#override-any-service-implementation)

```
// config/inventory.php
'bindings' => [
    'tenant_resolver'           => \App\Services\CurrentTenantResolver::class,
    'policy_resolver'           => \App\Services\DatabasePolicyResolver::class,
    'document_number_generator' => \App\Services\SequentialDocumentNumberGenerator::class,
    'stock_allocator'           => \App\Services\ZoneRestrictedAllocator::class,
    'stock_valuator'            => \App\Services\FifoValuator::class,
],
```

### Listen to inventory events

[](#listen-to-inventory-events)

```
// In EventServiceProvider:
protected $listen = [
    \Noman\Inventory\Domain\Inventory\Events\StockReceived::class => [
        \App\Listeners\NotifyPurchasingOnReceipt::class,
    ],
    \Noman\Inventory\Domain\Inventory\Events\BatchExpired::class => [
        \App\Listeners\AlertQualityControlTeam::class,
    ],
    \Noman\Inventory\Domain\Inventory\Events\StockReserved::class => [
        \App\Listeners\UpdateSalesOrderStatus::class,
    ],
];
```

### Custom item metadata

[](#custom-item-metadata)

Use the `metadata` JSON column on `inventory_items` for host-app-specific fields:

```
$item->update([
    'metadata' => [
        'cow_breed'    => 'Holstein',
        'farm_id'      => 42,
        'vaccination_status' => 'up_to_date',
    ],
]);
```

Or use the `inventory_custom_fields` + `inventory_custom_field_values` tables for structured custom field definitions.

---

API Endpoints
-------------

[](#api-endpoints)

All endpoints are prefixed with `/inventory` (configurable via `route_prefix`).

MethodEndpointDescriptionGET`/inventory/items`List itemsPOST`/inventory/items`Create itemGET`/inventory/items/{id}`Get itemPUT`/inventory/items/{id}`Update itemDELETE`/inventory/items/{id}`Delete itemPOST`/inventory/stock/receive`Receive stockPOST`/inventory/stock/issue`Issue stockPOST`/inventory/stock/transfer`Transfer stockPOST`/inventory/stock/adjust`Adjust stockPOST`/inventory/stock/reserve`Reserve stockDELETE`/inventory/stock/reserve/{id}`Release reservationGET`/inventory/documents`List documentsGET`/inventory/documents/{id}`Get documentPOST`/inventory/documents/{id}/post`Post documentPOST`/inventory/documents/{id}/reverse`Reverse documentPOST`/inventory/stock-counts/start`Start stock countPOST`/inventory/stock-counts/{id}/complete`Complete countGET`/inventory/reports/stock-on-hand`Stock on hand reportGET`/inventory/reports/stock-by-location`Stock by locationGET`/inventory/reports/stock-ledger`Stock movement ledgerGET`/inventory/reports/stock-card`Stock card (running balance)GET`/inventory/reports/batch-expiry`Batch expiry reportGET`/inventory/reports/inventory-aging`Inventory agingGET`/inventory/reports/valuation-summary`Valuation summaryGET`/inventory/reports/reservations`Reservation status---

Web UI (Blade)
--------------

[](#web-ui-blade)

The package includes an optional Blade-based web UI, using the same prefix as the API (default `/inventory`). Enable or disable it via `config('inventory.routes_enabled')`; middleware is set with `config('inventory.web_middleware', ['web'])`.

URLDescription`GET /inventory`Dashboard`GET /inventory/items`List items (create, edit, show, delete)`GET /inventory/warehouses`List warehouses (create, edit, show, delete)`GET /inventory/stock/receive`Receive stock form`GET /inventory/stock/issue`Issue stock form`GET /inventory/stock/transfer`Transfer stock form`GET /inventory/stock/adjust`Adjust stock form`GET /inventory/documents`Stock documents list and detail`GET /inventory/stock-counts`Stock count sessions; start new count`GET /inventory/reports`Reports index (stock on hand, by location, ledger, batch expiry, reservations)Views live in the package under `resources/views` and are registered with the `noman-inventory` view namespace. To customise the UI, publish the views:

```
php artisan vendor:publish --tag="noman-inventory-views"
```

Then edit the published Blade files in `resources/views/vendor/noman-inventory/`.

### Views not updating after `composer update`?

[](#views-not-updating-after-composer-update)

If you pushed changes to the package and ran `composer update` in your app but the UI still shows the old design, try this:

1. **Clear Laravel caches** (the app may be serving cached compiled views):

    ```
    php artisan view:clear
    php artisan cache:clear
    ```
2. **Published views override the package.** If you (or someone) ran `vendor:publish --tag=noman-inventory-views`, the app uses **copies** in `resources/views/vendor/noman-inventory/`. Those copies are **not** updated by `composer update`. To get the new package views again either:

    - Remove the published views so the app falls back to the package: ```
        # In your application root
        rm -rf resources/views/vendor/noman-inventory
        ```
    - Or re-publish and overwrite (this replaces your local customisations): ```
        php artisan vendor:publish --tag="noman-inventory-views" --force
        ```
3. **Ensure Composer is pulling the new version.** In the **package** repo, bump the version in `composer.json` (e.g. `"version": "1.0.6"`), commit and push. In the **application** that uses the package, run:

    ```
    composer update nomandev/noman-inventory
    ```

    If you use a branch or `dev` stability, ensure the app’s `composer.json` allows that version.

---

Changelog
---------

[](#changelog)

### v1.0.5 (2025-03-08)

[](#v105-2025-03-08)

- **MySQL compatibility:** Added short explicit names for all compound indexes and unique constraints to avoid MySQL's 64-character identifier limit.
- **Migration fixes:** Fixed variable shadowing in categories and locations migrations; corrected migration order (units/unit\_conversions before items) to satisfy foreign key dependencies.
- **Service provider:** Fixed `getPackageBaseDir()` so config and migrations publish from the correct path; added `bootPackageRoutes()` override for reliable route loading.
- **Laravel 11 support:** Package now supports both Laravel 11 and 12.

### v1.0.x (earlier)

[](#v10x-earlier)

- Initial release with append-only ledger, documents, batches, serials, expiry, FEFO/FIFO, valuation, reservations, stock counts, Blade UI, and REST API.

---

Running Tests
-------------

[](#running-tests)

```
cd packages/noman-inventory

# Install dev dependencies
composer install

# Run the full test suite
./vendor/bin/pest

# Run with coverage
./vendor/bin/pest --coverage
```

---

Architecture
------------

[](#architecture)

```
src/
├── Contracts/               # 7 interface contracts (public API seams)
├── Domain/
│   ├── Shared/
│   │   ├── Enums/           # MovementType, DocumentStatus, AllocationStrategy, ...
│   │   ├── ValueObjects/    # Quantity, Money, Sku, BatchCode, InventoryPolicy, ...
│   │   └── Exceptions/      # Domain exception hierarchy
│   └── Inventory/
│       └── Events/          # 12 domain events
├── Application/
│   ├── Actions/             # Use cases: Receive, Issue, Transfer, Adjust, Reserve, ...
│   ├── DTOs/                # Input/output data objects
│   └── Queries/             # Query parameter objects for reporting
└── Infrastructure/
    ├── Providers/           # NomanInventoryServiceProvider
    ├── Persistence/
    │   ├── Eloquent/        # 22 Eloquent models
    │   └── Repositories/    # 6 repository classes
    ├── Allocation/          # FefoAllocator (FEFO + FIFO)
    ├── Valuation/           # WeightedAverageValuator
    ├── Reporting/           # EloquentInventoryReporter
    ├── Listeners/           # Projection update listeners
    └── Support/             # NullTenantResolver, DefaultPolicyResolver, ...

```

---

License
-------

[](#license)

MIT

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance81

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

6

Last Release

102d ago

PHP version history (2 changes)v1.0.0PHP ^8.3

v1.0.1PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/3131078b09783fc26402ce99ef7a8a0681d6e73645a8fd3613567fd41c21c0bf?d=identicon)[noman](/maintainers/noman)

---

Top Contributors

[![nomanbhuiyan53](https://avatars.githubusercontent.com/u/23724575?v=4)](https://github.com/nomanbhuiyan53 "nomanbhuiyan53 (15 commits)")

---

Tags

laravelbatchmanagementmulti-tenantinventorystockledgerserialwarehousevaluation

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/nomandev-noman-inventory/health.svg)

```
[![Health](https://phpackages.com/badges/nomandev-noman-inventory/health.svg)](https://phpackages.com/packages/nomandev-noman-inventory)
```

###  Alternatives

[grumpydictator/firefly-iii

Firefly III: a personal finances manager.

23.8k69.4k](/packages/grumpydictator-firefly-iii)[firefly-iii/data-importer

Firefly III Data Import Tool.

7965.8k](/packages/firefly-iii-data-importer)[markwalet/nova-modal-response

A Laravel Nova asset for Modal responses on an action.

17818.7k](/packages/markwalet-nova-modal-response)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
