Fiscal Years & Periods
Fiscal years define the accounting periods for the company. When a fiscal year is created, 12 monthly periods are automatically generated. Journal entries are scoped to a fiscal year and period, and periods must be closed sequentially before the fiscal year can be closed.
Purpose
- Define annual accounting periods with start and end dates
- Auto-generate 12 monthly fiscal periods per year
- Control which periods accept journal entry postings
- Enforce sequential period closing
- Support fiscal year closing (which feeds into year-end closing)
Entity Attributes
Fiscal Year
| Field | Type | Description |
|---|---|---|
id | bigint | Primary key |
company_id | bigint | FK to companies |
name | string | English name (e.g., "FY 2026") |
name_ar | string | Arabic name (e.g., "السنة المالية 2026") |
start_date | date | First day of the fiscal year |
end_date | date | Last day of the fiscal year |
status | enum | open or closed |
created_at | timestamp | Creation timestamp |
updated_at | timestamp | Last update timestamp |
deleted_at | timestamp? | Soft-delete timestamp |
Indexes:
UNIQUE (company_id, name)
Fiscal Period
| Field | Type | Description |
|---|---|---|
id | bigint | Primary key |
company_id | bigint | FK to companies |
fiscal_year_id | bigint | FK to fiscal_years |
period_number | tinyint | Month number (1-12) |
name | string | English name (e.g., "January 2026") |
name_ar | string | Arabic name (e.g., "يناير 2026") |
start_date | date | First day of the period |
end_date | date | Last day of the period |
status | enum | open or closed |
created_at | timestamp | Creation timestamp |
updated_at | timestamp | Last update timestamp |
deleted_at | timestamp? | Soft-delete timestamp |
Indexes:
UNIQUE (fiscal_year_id, period_number)INDEX (fiscal_year_id)
Relationships
API Endpoints
Fiscal Years
| Method | Path | Description |
|---|---|---|
GET | /api/accounting/fiscal-years | List fiscal years |
POST | /api/accounting/fiscal-years | Create a fiscal year (auto-generates 12 periods) |
GET | /api/accounting/fiscal-years/{id} | Get a fiscal year with its periods |
DELETE | /api/accounting/fiscal-years/{id} | Delete a fiscal year |
POST | /api/accounting/fiscal-years/{id}/close | Close a fiscal year |
Fiscal Periods
| Method | Path | Description |
|---|---|---|
GET | /api/accounting/fiscal-periods | List fiscal periods (filterable by fiscal_year_id) |
GET | /api/accounting/fiscal-periods/{id} | Get a single fiscal period |
POST | /api/accounting/fiscal-periods/{id}/close | Hard-close a fiscal period |
POST | /api/accounting/fiscal-periods/{id}/soft-close | Soft-close a fiscal period |
POST | /api/accounting/fiscal-periods/{id}/reopen | Reopen a soft- or hard-closed period |
INFO
Fiscal years do not support an update endpoint. To change a fiscal year, delete it and re-create it (only possible if no journal entries exist).
Period Lifecycle
A fiscal period moves through three states:
| Status | Operational entries | Adjustment entries |
|---|---|---|
open | ✅ accepted | ✅ accepted |
soft_closed | ❌ rejected | ✅ accepted |
closed | ❌ rejected | ❌ rejected |
Soft-close is the reversible middle state used at period-end: day-to-day postings stop, but accountants can still book adjustment entries — accruals, FX revaluation (fx-revaluation), and zakat. Hard-close locks the period entirely. Both states can be reopened with the accounting.periods.reopen permission, provided the parent fiscal year is still open.
WARNING
A reversal journal entry cannot be back-dated before the original entry, and only resolves into an open period — it cannot be posted into a soft- or hard-closed period.
Request/Response Examples
Create Fiscal Year
Request POST /api/accounting/fiscal-years
curl -X POST https://moon-erp.test/api/accounting/fiscal-years \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "FY 2026",
"name_ar": "السنة المالية 2026",
"start_date": "2026-01-01",
"end_date": "2026-12-31"
}'final response = await http.post(
Uri.parse('https://moon-erp.test/api/accounting/fiscal-years'),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: jsonEncode({
'name': 'FY 2026',
'name_ar': 'السنة المالية 2026',
'start_date': '2026-01-01',
'end_date': '2026-12-31',
}),
);Response 201 Created
{
"data": {
"id": 1,
"company_id": 1,
"name": "FY 2026",
"name_en": "FY 2026",
"name_ar": "السنة المالية 2026",
"start_date": "2026-01-01",
"end_date": "2026-12-31",
"status": "open",
"status_label": "Open",
"periods": [
{
"id": 1,
"period_number": 1,
"name": "January 2026",
"name_ar": "يناير 2026",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"status": "open"
},
{
"id": 2,
"period_number": 2,
"name": "February 2026",
"name_ar": "فبراير 2026",
"start_date": "2026-02-01",
"end_date": "2026-02-28",
"status": "open"
}
],
"created_at": "2026-02-16T12:00:00.000000Z",
"updated_at": "2026-02-16T12:00:00.000000Z"
}
}Close a Fiscal Period
Request POST /api/accounting/fiscal-periods/1/close
Response 200 OK
{
"data": {
"id": 1,
"period_number": 1,
"name": "January 2026",
"name_ar": "يناير 2026",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"status": "closed",
"status_label": "Closed"
}
}Business Rules
- Auto-generation -- When a fiscal year is created, exactly 12 monthly periods are generated automatically, spanning from
start_datetoend_date. - Name uniqueness -- Fiscal year names must be unique per company.
- Date validation --
end_datemust be afterstart_date. Fiscal years cannot overlap for the same company. - No posting to closed periods -- Journal entries cannot be posted to a closed fiscal period.
- Sequential closing -- Fiscal periods must be closed in order (period 1 before period 2, etc.). You cannot close period 3 if period 2 is still open.
- Fiscal year close -- A fiscal year can only be closed when all 12 periods are closed.
- No deletion with entries -- A fiscal year cannot be deleted if any journal entries exist within it.
- Immutability -- Once created, fiscal year dates cannot be changed (no
updateendpoint).