Units
Moon ERP provides a two-tier measurement system: Unit Groups (e.g., Weight, Length, Volume) and Units (e.g., kg, g, lb). Each unit belongs to a group and has a conversion factor relative to the group's base unit. This system is used throughout the product catalog, inventory, and purchasing modules.
Unit Groups
Purpose
- Group related units of measure together (e.g., all weight units in one group)
- Provide an organizational structure for the unit catalog
Entity Attributes
| Field | Type | Description |
|---|---|---|
id | integer | Primary key |
company_id | integer | Foreign key to the company |
name | string | Group name (English, e.g., Weight) |
name_ar | string | Group name (Arabic, e.g., الوزن) |
is_active | boolean | Whether the group is active |
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
deleted_at | datetime? | Soft-delete timestamp |
API Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/core/unit-groups | List unit groups with their units (paginated) |
POST | /api/core/unit-groups | Create a unit group |
GET | /api/core/unit-groups/{id} | Get a unit group with its units |
PUT | /api/core/unit-groups/{id} | Update a unit group |
DELETE | /api/core/unit-groups/{id} | Soft-delete a unit group |
Units
Purpose
- Define individual units of measure with symbols and conversion factors
- Enable unit conversion via the base unit within each group
- Support product-level unit assignments for purchasing and selling
Entity Attributes
| Field | Type | Description |
|---|---|---|
id | integer | Primary key |
company_id | integer | Foreign key to the company |
unit_group_id | integer | Foreign key to the unit group |
name | string | Unit name (English, e.g., Kilogram) |
name_ar | string | Unit name (Arabic, e.g., كيلوغرام) |
symbol | string | Short symbol (e.g., kg, g, lb) |
conversion_factor | decimal(6) | Factor relative to the base unit |
is_base | boolean | Whether this is the base unit of its group |
is_active | boolean | Whether the unit is active |
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
deleted_at | datetime? | Soft-delete timestamp |
Conversion Factor Explained
The conversion_factor represents how many base units equal one of this unit. The base unit always has a factor of 1.000000.
Example: Weight group (base unit = Kilogram)
| Unit | Symbol | Conversion Factor | Meaning |
|---|---|---|---|
| Kilogram | kg | 1.000000 | Base unit |
| Gram | g | 0.001000 | 1 g = 0.001 kg |
| Pound | lb | 0.453592 | 1 lb = 0.453592 kg |
| Ton | ton | 1000.000000 | 1 ton = 1000 kg |
To convert from unit A to unit B: value_in_B = value_in_A * (factor_A / factor_B)
Relationships
API Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/core/units | List units (paginated, filterable by group) |
POST | /api/core/units | Create a unit |
GET | /api/core/units/{id} | Get a specific unit with its group |
PUT | /api/core/units/{id} | Update a unit |
DELETE | /api/core/units/{id} | Soft-delete a unit |
Query Parameters for GET /api/core/units
| Parameter | Type | Description |
|---|---|---|
unit_group_id | integer | Filter by unit group |
page | integer | Page number for pagination |
Request/Response Examples
GET /api/core/unit-groups
Retrieve unit groups with their units.
bash
curl -X GET https://moon-erp.test/api/core/unit-groups \
-H "Accept: application/json" \
-H "Accept-Language: ar" \
-H "Authorization: Bearer {token}"dart
final response = await http.get(
Uri.parse('https://moon-erp.test/api/core/unit-groups'),
headers: {
'Accept': 'application/json',
'Accept-Language': 'ar',
'Authorization': 'Bearer $token',
},
);Response 200 OK
json
{
"data": [
{
"id": 1,
"name": "الوزن",
"name_en": "Weight",
"name_ar": "الوزن",
"is_active": true,
"units": [
{
"id": 1,
"name": "كيلوغرام",
"name_en": "Kilogram",
"name_ar": "كيلوغرام",
"symbol": "kg",
"conversion_factor": "1.000000",
"is_base": true,
"is_active": true
},
{
"id": 2,
"name": "غرام",
"name_en": "Gram",
"name_ar": "غرام",
"symbol": "g",
"conversion_factor": "0.001000",
"is_base": false,
"is_active": true
}
],
"created_at": "2026-01-01T00:00:00.000000Z",
"updated_at": "2026-01-01T00:00:00.000000Z"
}
],
"links": { "first": "...?page=1", "last": "...?page=1", "prev": null, "next": null },
"meta": { "current_page": 1, "from": 1, "last_page": 1, "per_page": 25, "to": 1, "total": 1 }
}POST /api/core/unit-groups
Create a new unit group.
bash
curl -X POST https://moon-erp.test/api/core/unit-groups \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer {token}" \
-d '{
"name": "Length",
"name_ar": "الطول",
"is_active": true
}'dart
final response = await http.post(
Uri.parse('https://moon-erp.test/api/core/unit-groups'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
body: jsonEncode({
'name': 'Length',
'name_ar': 'الطول',
'is_active': true,
}),
);Response 201 Created
json
{
"data": {
"id": 2,
"name": "Length",
"name_en": "Length",
"name_ar": "الطول",
"is_active": true,
"units": [],
"created_at": "2026-02-16T10:00:00.000000Z",
"updated_at": "2026-02-16T10:00:00.000000Z"
}
}POST /api/core/units
Create a new unit within a group.
bash
curl -X POST https://moon-erp.test/api/core/units \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer {token}" \
-d '{
"unit_group_id": 2,
"name": "Meter",
"name_ar": "متر",
"symbol": "m",
"conversion_factor": 1.000000,
"is_base": true,
"is_active": true
}'dart
final response = await http.post(
Uri.parse('https://moon-erp.test/api/core/units'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
body: jsonEncode({
'unit_group_id': 2,
'name': 'Meter',
'name_ar': 'متر',
'symbol': 'm',
'conversion_factor': 1.000000,
'is_base': true,
'is_active': true,
}),
);Response 201 Created
json
{
"data": {
"id": 3,
"unit_group_id": 2,
"name": "Meter",
"name_en": "Meter",
"name_ar": "متر",
"symbol": "m",
"conversion_factor": "1.000000",
"is_base": true,
"is_active": true,
"unit_group": { "id": 2, "name": "Length" },
"created_at": "2026-02-16T10:00:00.000000Z",
"updated_at": "2026-02-16T10:00:00.000000Z"
}
}Business Rules
- One base unit per group -- each unit group must have exactly one unit with
is_base: trueandconversion_factor: 1.000000. - Conversion factor required -- every unit must specify a
conversion_factorrelative to the base unit. - Company scoping -- unit groups and units are scoped to the authenticated user's company.
- Units depend on groups -- every unit must belong to a
unit_group_id. Deleting a group that still has units should be prevented. - Used by products -- units are referenced by the
Product.base_unit_idfield and byProductUnitrecords. Deleting a unit in use may cause referential integrity issues. - Symbol is for display -- the
symbolfield (e.g.,kg,m,pcs) is used for UI display alongside quantities.