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)
| Field | Type | Description |
|---|---|---|
id | bigint | Primary key |
company_id | bigint | FK to companies |
issue_number | string(30) | Auto-generated unique number (e.g., GDN-000001) |
date | date | Issue date |
warehouse_id | bigint | FK to warehouses (source warehouse) |
partner_id | bigint? | FK to business_partners (customer) |
reference_type | enum | sale, consumption, damage, adjustment, other |
reference_number | string? | External reference (e.g., SO number) |
status | enum | draft, approved, cancelled |
total_quantity | decimal(15,3) | Sum of item quantities |
total_cost | decimal(15,3) | Sum of item total costs (set on approval) |
notes | text? | Additional notes |
notes_ar | text? | Arabic notes |
approved_by | bigint? | FK to users |
approved_at | timestamp? | Approval timestamp |
cancelled_by | bigint? | FK to users |
cancelled_at | timestamp? | Cancellation timestamp |
created_by | bigint? | FK to users |
created_at | timestamp | Creation timestamp |
updated_at | timestamp | Last update timestamp |
deleted_at | timestamp? | Soft-delete timestamp |
Indexes:
UNIQUE (company_id, issue_number)INDEX (warehouse_id)INDEX (partner_id)INDEX (status)INDEX (date)
Inventory Issue Item
| Field | Type | Description |
|---|---|---|
id | bigint | Primary key |
inventory_issue_id | bigint | FK to inventory_issues |
product_id | bigint | FK to products |
product_variant_id | bigint? | FK to product_variants |
unit_id | bigint | FK to units |
quantity | decimal(15,3) | Issued quantity |
unit_cost | decimal(15,3) | Cost per unit (auto-set from average cost on approval) |
total_cost | decimal(15,3) | quantity × unit_cost (auto-set on approval) |
batch_number | string? | Batch/lot number |
notes | text? | Item notes |
created_at | timestamp | Creation timestamp |
updated_at | timestamp | Last update timestamp |
Indexes:
INDEX (inventory_issue_id)INDEX (product_id)
Relationships
API Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/inventory/issues | List issues (paginated, filterable) |
POST | /api/inventory/issues | Create 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}/approve | Approve and update stock |
POST | /api/inventory/issues/{id}/cancel | Cancel and reverse stock |
Query Parameters (List)
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (default: 1) |
status | string | Filter by status (draft, approved, cancelled) |
warehouse_id | integer | Filter by warehouse |
partner_id | integer | Filter by customer |
reference_type | string | Filter by reference type |
date_from | date | Filter issues from this date |
date_to | date | Filter issues up to this date |
search | string | Search 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.
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"
}
]
}'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
{
"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:
- Checks stock — verifies sufficient quantity exists for each item (unless
allow_negative_stockis enabled on the warehouse). - Sets unit cost — each item's
unit_costis set from the product's currentStockBalance.average_cost. - Decreases stock — stock balances are reduced via
StockService.decreaseStock(). - Records movements — a stock movement record is created for each item.
Response 200 OK
{
"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
{
"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
{
"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
- Auto-generated number — The
issue_numberis assigned automatically via the Sequence system (prefixGDN). Do not pass it in the request. - Items required — An issue must have at least one item.
- No cost at creation — Items are created with
unit_cost = 0andtotal_cost = 0. Costs are set automatically on approval. - Auto-costing on approval — Each item's
unit_costis set fromStockBalance.average_costfor the product in the source warehouse. - 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.
- Negative stock override — If the warehouse has
allow_negative_stock = true, the stock check is bypassed. - Draft-only edits — Only issues in
draftstatus can be updated or deleted. - Approval updates stock — On approval,
StockBalancequantities are decreased viaStockService.decreaseStock(). - Cancellation reverses stock — Cancelling an approved issue increases stock balances back and creates reversal movement records.
- Cannot cancel draft — Only
approvedissues can be cancelled. - Cannot approve twice — Attempting to approve a non-draft issue returns an error.
- Audit trail —
created_by,approved_by, andcancelled_bytrack the user at each lifecycle step.
Workflow / State Diagram
Step-by-Step: Create and Approve an Issue
Reference Types
| Type | Value | Usage |
|---|---|---|
| Sale | sale | Goods issued for a sales order |
| Consumption | consumption | Internal consumption (e.g., manufacturing) |
| Damage | damage | Damaged or defective goods write-off |
| Adjustment | adjustment | Inventory adjustment (decrease) |
| Other | other | Miscellaneous issues |