Skip to content

Expenses

Expenses provide a simplified entry form for recording company expenditures. Each expense is linked to an expense category, a paying account, and optionally a tax rate, cost center, and business partner. When approved, the system automatically creates a balanced journal entry. Cancellation reverses the journal entry.

Purpose

  • Record expense transactions with a streamlined single-entry form
  • Auto-calculate tax amounts when a tax rate is assigned
  • Auto-generate document numbers via the sequence service
  • Create journal entries automatically on approval
  • Reverse journal entries on cancellation
  • Support filtering and reporting by category, partner, branch, and date range

Entity Attributes

Expense

FieldTypeDescription
idbigintPrimary key
company_idbigintFK to companies
branch_idbigint?FK to branches
document_numberstringAuto-generated sequence number (e.g., EXP-2026-0001)
datedateExpense date
expense_category_idbigintFK to transaction_categories (type=expense)
descriptionstringEnglish description
description_arstring?Arabic description
amountdecimal(12,3)Base expense amount (before tax)
tax_rate_idbigint?FK to tax_rates
tax_amountdecimal(12,3)Auto-calculated tax amount
total_amountdecimal(12,3)amount + tax_amount
payment_methodenumcash, check, bank_transfer, card, mixed
paying_account_idbigintFK to accounts (cash or bank GL account used for payment)
bank_account_idbigint?FK to bank_accounts (required for check/bank_transfer)
check_numberstring?Check number (if payment by check)
cost_center_idbigint?FK to cost_centers
partner_idbigint?FK to business_partners
statusenumdraft, approved, cancelled
journal_entry_idbigint?FK to journal_entries (set on approval)
notestext?Additional notes
created_bybigintFK to users
approved_bybigint?FK to users
approved_atdatetime?Approval timestamp
created_attimestampCreation timestamp
updated_attimestampLast update timestamp
deleted_attimestamp?Soft-delete timestamp

Relationships

Lifecycle

API Endpoints

MethodPathDescription
GET/api/accounting/expensesList expenses (paginated)
POST/api/accounting/expensesCreate a new expense (draft)
GET/api/accounting/expenses/{id}Get a single expense
PUT/api/accounting/expenses/{id}Update an expense (draft only)
DELETE/api/accounting/expenses/{id}Soft-delete an expense (draft only)
POST/api/accounting/expenses/{id}/approveApprove and create journal entry
POST/api/accounting/expenses/{id}/cancelCancel and reverse journal entry

Query Parameters

ParameterTypeRequiredDescription
statusstringNoFilter by status: draft, approved, cancelled
expense_category_idintegerNoFilter by expense category
partner_idintegerNoFilter by business partner
branch_idintegerNoFilter by branch
date_fromdateNoFilter expenses on or after this date
date_todateNoFilter expenses on or before this date
pageintegerNoPage number (25 per page)

Request/Response Examples

Create Expense

Request POST /api/accounting/expenses

bash
curl -X POST https://moon-erp.test/api/accounting/expenses \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "date": "2026-02-23",
    "expense_category_id": 1,
    "description": "Monthly office rent",
    "description_ar": "إيجار المكتب الشهري",
    "amount": "1500.000",
    "payment_method": "bank_transfer",
    "paying_account_id": 15,
    "bank_account_id": 1,
    "tax_rate_id": 1,
    "cost_center_id": 2,
    "partner_id": 5,
    "branch_id": 1,
    "notes": "February 2026 rent"
  }'
dart
final response = await http.post(
  Uri.parse('https://moon-erp.test/api/accounting/expenses'),
  headers: {
    'Authorization': 'Bearer $token',
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
  body: jsonEncode({
    'date': '2026-02-23',
    'expense_category_id': 1,
    'description': 'Monthly office rent',
    'description_ar': 'إيجار المكتب الشهري',
    'amount': '1500.000',
    'payment_method': 'bank_transfer',
    'paying_account_id': 15,
    'bank_account_id': 1,
    'tax_rate_id': 1,
    'cost_center_id': 2,
    'partner_id': 5,
    'branch_id': 1,
    'notes': 'February 2026 rent',
  }),
);

Response 201 Created

json
{
  "data": {
    "id": 1,
    "company_id": 1,
    "branch_id": 1,
    "document_number": "EXP-2026-0001",
    "date": "2026-02-23",
    "expense_category_id": 1,
    "expense_category": {
      "id": 1,
      "code": "EXP-RENT",
      "name": "Office Rent",
      "name_ar": "إيجار المكتب"
    },
    "description": "Monthly office rent",
    "description_ar": "إيجار المكتب الشهري",
    "amount": "1500.000",
    "tax_rate_id": 1,
    "tax_rate": {
      "id": 1,
      "name": "VAT 5%",
      "rate": "5.0000"
    },
    "tax_amount": "75.000",
    "total_amount": "1575.000",
    "payment_method": "bank_transfer",
    "payment_method_label": "Bank Transfer",
    "paying_account_id": 15,
    "paying_account": {
      "id": 15,
      "code": "1201001",
      "name": "NBK Main Account"
    },
    "bank_account_id": 1,
    "check_number": null,
    "cost_center_id": 2,
    "partner_id": 5,
    "partner": {
      "id": 5,
      "name": "Al Salam Real Estate",
      "name_ar": "شركة السلام العقارية"
    },
    "status": "draft",
    "status_label": "Draft",
    "journal_entry_id": null,
    "notes": "February 2026 rent",
    "created_by": 1,
    "approved_by": null,
    "approved_at": null,
    "created_at": "2026-02-23T10:00:00.000000Z",
    "updated_at": "2026-02-23T10:00:00.000000Z"
  }
}

Approve Expense

Request POST /api/accounting/expenses/1/approve

Response 200 OK

json
{
  "data": {
    "id": 1,
    "document_number": "EXP-2026-0001",
    "status": "approved",
    "status_label": "Approved",
    "journal_entry_id": 42,
    "approved_by": 1,
    "approved_at": "2026-02-23T11:00:00.000000Z"
  }
}

List Expenses

Request GET /api/accounting/expenses?status=approved&date_from=2026-01-01&date_to=2026-12-31

Response 200 OK

json
{
  "data": [
    {
      "id": 1,
      "document_number": "EXP-2026-0001",
      "date": "2026-02-23",
      "expense_category": {
        "id": 1,
        "name": "إيجار المكتب"
      },
      "amount": "1500.000",
      "tax_amount": "75.000",
      "total_amount": "1575.000",
      "payment_method": "bank_transfer",
      "paying_account": {
        "id": 15,
        "code": "1201001",
        "name": "NBK Main Account"
      },
      "partner": {
        "id": 5,
        "name": "شركة السلام العقارية"
      },
      "status": "approved"
    }
  ],
  "links": { "first": "...", "last": "...", "prev": null, "next": null },
  "meta": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1 }
}

Generated Journal Entry

When an expense is approved, the system creates a posted journal entry:

LineAccountDebitCreditDescription
1Expense category GL account (e.g., Rent Expense)1,500.000--Expense amount
2Tax Payable account (from tax rate)75.000--Tax amount (if applicable)
3Paying account (e.g., NBK Main Account)--1,575.000Total payment

Business Rules

RuleDescription
Draft-only editsExpenses can only be updated or deleted while in draft status
Auto document numberdocument_number is auto-generated via the sequence service on creation
Tax auto-calculationIf tax_rate_id is provided, tax_amount = amount * rate / 100 and total_amount = amount + tax_amount
Tax recalculationOn update, tax is recalculated if amount or tax_rate_id changes
Approval creates JEApproving a draft expense creates a posted journal entry and sets approved_by and approved_at
Cancel reverses JECancelling an approved expense reverses the associated journal entry
Company scopedAll expenses are filtered by the authenticated user's company
Bank details requiredbank_account_id is required when payment_method is check or bank_transfer

Permissions

PermissionDescription
accounting.expenses.viewView expenses
accounting.expenses.createCreate new expenses
accounting.expenses.updateUpdate draft expenses
accounting.expenses.approveApprove draft expenses
accounting.expenses.cancelCancel approved expenses

Moon ERP API Documentation