The Inventory System extension manages a mobile hotspot/device lending program built on top of CiviCRM Membership. It tracks physical devices (hotspots, SIMs) from warehouse stock through assignment to a member, shipping (with label purchase/tracking via a companion Shippo integration), warranty replacement, and eventual expiration/return. It also handles membership-linked device eligibility, recurring billing plans, and member-to-member referral codes that extend membership.
The extension is licensed under AGPL-3.0.
- PHP v7.2+
- CiviCRM 5.51+
Learn more about installing CiviCRM extensions in the CiviCRM Sysadmin Guide.
Sysadmins and developers may download the .zip file for this extension and
install it with the command-line tool cv.
cd <extension-dir>
cv dl com.skvare.inventory@https://github.com/FIXME/com.skvare.inventory/archive/master.zipSysadmins and developers may clone the Git repo for this extension and install it with the command-line tool cv.
git clone https://github.com/FIXME/com.skvare.inventory.git
cv en inventoryAfter enabling the extension, a new Inventory menu appears in the CiviCRM navigation (visible
to users holding the access Inventory permission), with:
- Dashboard (
civicrm/inventory) — tabs for Devices, Orders, Shipping, and Settings, plus summary stats. - Shipping (
civicrm/inventory/shipments) — shipment batches, label printing, tracking. - Inventory Settings (
civicrm/inventory/setting, admin only) — stock reorder lead time and related configuration.
It also adds a devices/orders tab to contact records, and extra fields (renewal allowed, shippable countries, fair value, category) plus a Product ↔ Membership mapping screen on Membership Type admin pages, since devices are tied to membership types.
Key concepts:
- Product — a general device model/catalog entry (e.g. a specific hotspot model), with pricing, stock thresholds, and warehouse location.
- Device (product variant) — one specific physical unit (identified by MEID/IMEI) assigned to a member; this is what gets created/assigned/suspended/terminated/replaced day to day.
Three scheduled jobs run automatically once installed: an hourly job that recalculates reorder points and flags low/out-of-stock products, a daily job that expires devices past their end date and flags "problem" devices, and an hourly job that cancels stale unpaid orders and sends shipping/tracking notification emails.
Access is gated by a set of custom permissions (access Inventory, administer Inventory,
access shipment, access My Contact Product / access All Contact Product, and
create/edit/access/delete permissions per entity — see CRM/Inventory/Permission.php), so what a
given user sees in the Inventory menu depends on their role.
The extension defines 19 custom tables (all prefixed civicrm_inventory_) and extends 2 core
CiviCRM tables. Grouped by area:
| Table | Purpose | Relates to |
|---|---|---|
civicrm_inventory_category |
Product category, hierarchical (e.g. Mobile, Hotspot, Donation, T-Shirt) | parent_id → self |
civicrm_inventory_product |
A device model in the catalog — pricing, stock thresholds, reorder point, warehouse/shelf location | product_category_id → Category; warehouse_id → Warehouse |
civicrm_inventory_product_meta (Not in Use) |
Free-form key/value metadata attached to a product | product_id → Product |
civicrm_inventory_product_variant |
One physical device unit (MEID/IMEI), its lifecycle status, and who it's assigned to | product_id → Product; contact_id → Contact; membership_id → Membership; sales_id → Sales; replaced_product_id → self (previous unit, if replaced) |
civicrm_inventory_product_variant_replacement |
Records a warranty replacement (old unit → new unit) | old_product_id/new_product_id → Product Variant; contact_id → Contact |
civicrm_inventory_product_changelog |
Audit trail of device status changes, grouped into batches | product_variant_id → Product Variant; batch_id → Batch; contact_id → Contact |
civicrm_inventory_batch |
A named/exportable run of changelog entries | created_id → Contact |
civicrm_inventory_warehouse |
Physical warehouse location | address_id → Address |
civicrm_inventory_warehouse_transfer |
Stock transfer of a device between warehouses/contacts | product_variant_id → Product Variant; from_warehouse_id/to_warehouse_id → Warehouse; from_contact_id/to_contact_id → Contact |
| Table | Purpose | Relates to |
|---|---|---|
civicrm_inventory_supplier |
Vendor/supplier | contact_id → Contact; address_id → Address |
civicrm_inventory_purchase_order |
A purchase order placed with a supplier | contact_id → Contact; supplier_id → Supplier |
civicrm_inventory_purchase_order_detail |
Line item of a purchase order | order_id → Purchase Order; product_variant_id → Product Variant; supplier_id → Supplier; warehouse_id → Warehouse |
| Table | Purpose | Relates to |
|---|---|---|
civicrm_inventory_product_membership |
Which product models are available/allowed for which membership type | product_id → Product; membership_type_id → Membership Type; contact_id → Contact |
civicrm_inventory_billing_plans (Not in Use) |
Recurring billing plan (amount, frequency, installments) per membership type | membership_type_id → Membership Type |
civicrm_inventory_referrals |
Member-to-member referral code that extends membership on redemption | creator_id/consumer_id → Membership |
| Table | Purpose | Relates to |
|---|---|---|
civicrm_inventory_sales |
A customer order/sale | contact_id → Contact; contribution_id → Contribution; product_id → Product; shipment_id → Shipment |
civicrm_inventory_sales_detail |
Line item of a sale | sales_id → Sales; product_variant_id → Product Variant; warehouse_id → Warehouse; membership_id → Membership; contribution_id → Contribution |
civicrm_inventory_shipment |
A shipping batch grouping up to 50 sales | contact_id/modified_id → Contact |
civicrm_inventory_shipment_labels |
Purchased Shippo shipping label/tracking record for one sale | sales_id → Sales; contact_id → Contact |
| Table | Added columns | Purpose |
|---|---|---|
civicrm_membership_type |
category, may_renew, shippable_to, fair_value |
Marks which membership types include a device, whether they auto-renew, which countries they can ship to, and the device's fair market value |
civicrm_line_item |
product_id, product_variant_id, sale_id, membership_id, subtitle, type, additional_details |
Ties a CiviCRM contribution's line item back to the specific device/sale it paid for |
Supplier ──> Purchase Order ──> Purchase Order Detail ──> Product Variant (received into) ──> Warehouse
│
Category ──> Product <───────────────────────────────────────────┘
│ │
│ ├──> Product ↔ Membership Type mapping ──> Membership Type ──> Billing Plan
│ └──> Product Variant (Device) ──> Contact / Membership
│ │
│ ├──> Sales / Sales Detail ──> Contribution
│ ├──> Product Changelog ──> Batch (status audit trail)
│ ├──> Warehouse Transfer (moved between warehouses)
│ └──> Variant Replacement (warranty swap: old unit → new unit)
│
Sales ──> Shipment ──> Shipment Labels (Shippo rate/label purchase, tracking)
Membership ──> Referral (creator/consumer) ──> extends Membership end date on redemption
In short: a Product (catalog model) is stocked into a Warehouse via a Purchase Order, becomes an individual Product Variant (physical device), gets sold/assigned to a Contact through a Sale tied to a Membership, is grouped into a Shipment for label purchase/tracking, and every status change along the way is recorded in the Changelog/Batch trail. Referrals and Billing Plans sit alongside this to extend membership and drive recurring pricing, independent of any specific device.
This extension is at alpha stage (v1.0) and is under active development:
- The Dashboard's device-listing tab is explicitly marked "(WIP)" in the UI.
- The Settings page links to a "Shippo Setting" page for configuring shipping label purchasing/rates; that page is provided by a separate, companion Shippo extension and only resolves where that extension is also installed.
- International shipping: customs handling is incomplete for some EU countries
(
CRM/Inventory/BAO/InventoryShipmentLabels.php). - Referral codes: there is a known unhandled edge case where a membership could theoretically be
extended twice from overlapping referrals (
CRM/Inventory/BAO/InventoryReferrals.php). - The Order Sweeper scheduled job's legacy "remove expired enrollments" step is currently disabled/no-op, and has no automated test coverage.
- There is no dedicated reporting module — reporting is done via the SearchKit-based list screens under the Inventory menu rather than CiviCRM's Report module.
