Skip to content

Stock Issues (GDN)

Stock issues represent goods dispatched from a warehouse. Each issue contains one or more items specifying the product and quantity. Unlike receipts, item costs are not set at creation — they are automatically populated from the weighted-average cost on approval. When approved, the issue decreases stock balances and records stock movements.

Purpose

  • Record outgoing goods with quantity per item
  • Auto-set unit cost from weighted-average cost on approval
  • Check sufficient stock before approval (unless warehouse allows negative stock)
  • Update stock balances on approval
  • Create stock movement audit records on approval
  • Reverse stock changes on cancellation
  • Generate issue numbers automatically via the Sequence system

Entity Attributes

Inventory Issue (Header)

FieldTypeDescription
idbigintPrimary key
company_idbigintFK to companies
issue_numberstring(30)Auto-generated unique number (e.g., GDN-000001)
datedateIssue date
warehouse_idbigintFK to warehouses (source warehouse)
partner_idbigint?FK to business_partners (customer)
reference_typeenumsale, consumption, damage, adjustment, other
reference_numberstring?External reference (e.g., SO number)
statusenumdraft, approved, cancelled
total_quantitydecimal(15,3)Sum of item quantities
total_costdecimal(15,3)Sum of item total costs (set on approval)
notestext?Additional notes
notes_artext?Arabic notes
approved_bybigint?FK to users
approved_attimestamp?Approval timestamp
cancelled_bybigint?FK to users
cancelled_attimestamp?Cancellation timestamp
created_bybigint?FK to users
created_attimestampCreation timestamp
updated_attimestampLast update timestamp
deleted_attimestamp?Soft-delete timestamp

Indexes:

  • UNIQUE (company_id, issue_number)
  • INDEX (warehouse_id)
  • INDEX (partner_id)
  • INDEX (status)
  • INDEX (date)

Inventory Issue Item

FieldTypeDescription
idbigintPrimary key
inventory_issue_idbigintFK to inventory_issues
product_idbigintFK to products
product_variant_idbigint?FK to product_variants
unit_idbigintFK to units
quantitydecimal(15,3)Issued quantity
unit_costdecimal(15,3)Cost per unit (auto-set from average cost on approval)
total_costdecimal(15,3)quantity × unit_cost (auto-set on approval)
batch_numberstring?Batch/lot number
notestext?Item notes
created_attimestampCreation timestamp
updated_attimestampLast update timestamp

Indexes:

  • INDEX (inventory_issue_id)
  • INDEX (product_id)

Relationships

API Endpoints

MethodPathDescription
GET/api/inventory/issuesList issues (paginated, filterable)
POST/api/inventory/issuesCreate a draft issue with items
GET/api/inventory/issues/{id}Get issue with items and relations
PUT/api/inventory/issues/{id}Update a draft issue
DELETE/api/inventory/issues/{id}Delete a draft issue
POST/api/inventory/issues/{id}/approveApprove and update stock
POST/api/inventory/issues/{id}/cancelCancel and reverse stock

Query Parameters (List)

ParameterTypeDescription
pageintegerPage number (default: 1)
statusstringFilter by status (draft, approved, cancelled)
warehouse_idintegerFilter by warehouse
partner_idintegerFilter by customer
reference_typestringFilter by reference type
date_fromdateFilter issues from this date
date_todateFilter issues up to this date
searchstringSearch by issue number or reference number

Request/Response Examples

Create Issue

Request POST /api/inventory/issues

No Cost Required

Unlike receipts, you do not need to provide unit_cost when creating an issue. The cost is automatically set from the product's current weighted-average cost when the issue is approved.

bash
curl -X POST https://moon-erp.test/api/inventory/issues \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "date": "2026-02-22",
    "warehouse_id": 1,
    "partner_id": 10,
    "reference_type": "sale",
    "reference_number": "SO-2026-001",
    "notes": "Goods dispatched to customer",
    "notes_ar": "بضاعة صادرة للعميل",
    "items": [
      {
        "product_id": 1,
        "unit_id": 1,
        "quantity": "20.000"
      },
      {
        "product_id": 2,
        "unit_id": 1,
        "quantity": "10.000"
      }
    ]
  }'
dart
final response = await http.post(
  Uri.parse('https://moon-erp.test/api/inventory/issues'),
  headers: {
    'Authorization': 'Bearer $token',
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
  body: jsonEncode({
    'date': '2026-02-22',
    'warehouse_id': 1,
    'partner_id': 10,
    'reference_type': 'sale',
    'reference_number': 'SO-2026-001',
    'notes': 'Goods dispatched to customer',
    'notes_ar': 'بضاعة صادرة للعميل',
    'items': [
      {
        'product_id': 1,
        'unit_id': 1,
        'quantity': '20.000',
      },
      {
        'product_id': 2,
        'unit_id': 1,
        'quantity': '10.000',
      },
    ],
  }),
);

Response 201 Created

json
{
  "data": {
    "id": 1,
    "company_id": 1,
    "issue_number": "GDN-000001",
    "date": "2026-02-22",
    "warehouse_id": 1,
    "partner_id": 10,
    "reference_type": "sale",
    "reference_type_label": "Sale",
    "reference_number": "SO-2026-001",
    "status": "draft",
    "status_label": "Draft",
    "total_quantity": "30.000",
    "total_cost": "0.000",
    "notes": "Goods dispatched to customer",
    "notes_ar": "بضاعة صادرة للعميل",
    "approved_by": null,
    "approved_at": null,
    "cancelled_by": null,
    "cancelled_at": null,
    "created_by": 1,
    "warehouse": { "id": 1, "name": "Main Warehouse" },
    "partner": { "id": 10, "name": "Customer Corp." },
    "items": [
      {
        "id": 1,
        "product_id": 1,
        "product_variant_id": null,
        "unit_id": 1,
        "quantity": "20.000",
        "unit_cost": "0.000",
        "total_cost": "0.000",
        "batch_number": null,
        "product": { "id": 1, "name": "Widget A" },
        "unit": { "id": 1, "name": "Piece" }
      },
      {
        "id": 2,
        "product_id": 2,
        "product_variant_id": null,
        "unit_id": 1,
        "quantity": "10.000",
        "unit_cost": "0.000",
        "total_cost": "0.000",
        "batch_number": null,
        "product": { "id": 2, "name": "Widget B" },
        "unit": { "id": 1, "name": "Piece" }
      }
    ],
    "created_at": "2026-02-22T11:00:00.000000Z",
    "updated_at": "2026-02-22T11:00:00.000000Z"
  }
}

Approve Issue

Request POST /api/inventory/issues/1/approve

Auto-Costing & Stock Check

On approval, the system:

  1. Checks stock — verifies sufficient quantity exists for each item (unless allow_negative_stock is enabled on the warehouse).
  2. Sets unit cost — each item's unit_cost is set from the product's current StockBalance.average_cost.
  3. Decreases stock — stock balances are reduced via StockService.decreaseStock().
  4. Records movements — a stock movement record is created for each item.

Response 200 OK

json
{
  "data": {
    "id": 1,
    "issue_number": "GDN-000001",
    "status": "approved",
    "status_label": "Approved",
    "total_cost": "637.500",
    "approved_by": 1,
    "approved_at": "2026-02-22T11:05:00.000000Z",
    "items": [
      {
        "id": 1,
        "quantity": "20.000",
        "unit_cost": "25.500",
        "total_cost": "510.000"
      },
      {
        "id": 2,
        "quantity": "10.000",
        "unit_cost": "12.750",
        "total_cost": "127.500"
      }
    ]
  }
}

Insufficient Stock Error

Response 500 Internal Server Error

json
{
  "message": "Insufficient stock for product Widget A in warehouse Main Warehouse. Available: 15.000, Requested: 20.000"
}

Cancel Issue

Request POST /api/inventory/issues/1/cancel

Stock Reversal

Cancellation reverses the stock changes: quantities are increased back and values are adjusted. A new stock movement is recorded as a reversal.

Response 200 OK

json
{
  "data": {
    "id": 1,
    "issue_number": "GDN-000001",
    "status": "cancelled",
    "status_label": "Cancelled",
    "cancelled_by": 1,
    "cancelled_at": "2026-02-22T11:10:00.000000Z"
  }
}

Business Rules

  1. Auto-generated number — The issue_number is assigned automatically via the Sequence system (prefix GDN). Do not pass it in the request.
  2. Items required — An issue must have at least one item.
  3. No cost at creation — Items are created with unit_cost = 0 and total_cost = 0. Costs are set automatically on approval.
  4. Auto-costing on approval — Each item's unit_cost is set from StockBalance.average_cost for the product in the source warehouse.
  5. Sufficient stock check — On approval, the system verifies that the warehouse has enough stock for each item. If insufficient, the approval fails with an error message specifying the product, available quantity, and requested quantity.
  6. Negative stock override — If the warehouse has allow_negative_stock = true, the stock check is bypassed.
  7. Draft-only edits — Only issues in draft status can be updated or deleted.
  8. Approval updates stock — On approval, StockBalance quantities are decreased via StockService.decreaseStock().
  9. Cancellation reverses stock — Cancelling an approved issue increases stock balances back and creates reversal movement records.
  10. Cannot cancel draft — Only approved issues can be cancelled.
  11. Cannot approve twice — Attempting to approve a non-draft issue returns an error.
  12. Audit trailcreated_by, approved_by, and cancelled_by track the user at each lifecycle step.

Workflow / State Diagram

Step-by-Step: Create and Approve an Issue

Reference Types

TypeValueUsage
SalesaleGoods issued for a sales order
ConsumptionconsumptionInternal consumption (e.g., manufacturing)
DamagedamageDamaged or defective goods write-off
AdjustmentadjustmentInventory adjustment (decrease)
OtherotherMiscellaneous issues

Moon ERP API Documentation