# Core Module — Business and Functional Documentation

## What the Core Module Is Responsible For

The Core module is the foundational layer of the ERP system. It manages three pieces of infrastructure that every other module depends on: branches (physical or logical operating units), system-wide settings (configuration values that control behaviour across modules), and number sequences (the counters that generate unique document numbers for transactions).

No business transaction — an invoice, a payroll run, a purchase order, a journal entry — can be completed without the Core module functioning correctly. Branches define the organisational context in which transactions are recorded. Settings gate posting behaviour, currency choices, and language. Number sequences ensure that every document carries a unique, formatted identifier.

---

## Sub-Feature: Branches

### What a Branch Represents

A branch is a named, codified operating unit. It can represent a physical office, a warehouse location, a company region, or any other organisational division that needs to be tracked separately. Every branch carries its own contact details, address, and manager information, and its own status flags (active/inactive, main/non-main).

One branch at a time is designated the "main" branch. The main flag has meaning wherever the system falls back to a default location, for example when selecting a default warehouse or when displaying the primary company identity on printed documents.

### Branch Scoping Across Modules

When other modules record transactions, they optionally scope those transactions to a branch. This shows up in two concrete ways:

- Number sequences can be scoped per branch, meaning a Riyadh branch and a Jeddah branch can each maintain their own invoice counter independently, both starting from 00001 in each calendar year.
- Settings can be scoped either at the system level (applying to all branches) or at the branch level (overriding the system value for one branch). When a module reads a setting, it checks for a branch-specific override first and falls back to the system-level value.

This means branch management is a prerequisite step before configuring the rest of the system. Other modules (Finance, Inventory, Sales, HR) all accept an optional branch identifier and rely on branches already being created in Core.

### Actions a User Can Perform on Branches

#### List Branches

The user navigates to the branch index page. The system displays all non-deleted branches in a paginated list (15 per page). The list can be filtered by:

- Free-text search across the branch code, name, email, and phone fields.
- Active status (active, inactive, or all).
- Main branch flag (main-only or all).

The list can be sorted by any column (defaulting to newest-first by creation date). The filters persist in the URL query string so the user can share or bookmark a filtered view.

Required permission: `core.branches.view`.

#### View a Single Branch

The user clicks through to the branch detail page. The system renders all stored fields: identifiers, contact information, address, manager details, status flags, and the timestamps for when the branch was activated or deactivated. The audit trail (who created and who last updated the branch) is also shown.

Required permission: `core.branches.view`.

#### Create a Branch

The user opens the create form. The required fields are the branch code and the branch name. All other fields are optional.

Validation enforced:

- Code: required, string, max 20 characters, must be unique across all branches.
- Name: required, string, max 255 characters.
- Slug: optional; if left blank, the system derives it automatically from the name using Laravel's slug helper (e.g. "Main Office" becomes "main-office"). The system then ensures the slug is unique by appending a numeric suffix if a collision exists.
- Email and manager email: must be valid email format when provided.
- Phone, mobile, fax, manager phone: string, max 20 characters.
- Address: max 500 characters.
- City, state, country: max 100 characters.
- Postal code: max 20 characters.
- Description: max 1,000 characters.
- Manager name: max 255 characters.
- is_active and is_main: boolean flags, defaulting to false if not submitted.

When the branch is saved in an active state, the system records the current timestamp in the `activated_at` field. The currently authenticated admin's ID is stored as `created_by` and `updated_by`.

On success, the user is redirected to the new branch's detail page.

Required permission: `core.branches.create`.

#### Edit a Branch

The user opens the edit form for an existing branch. The same validation rules apply as for create, with one difference: the uniqueness checks for code and slug exclude the branch being edited so the branch can keep its own values unchanged.

If the name changes, the system regenerates the slug from the new name and ensures it remains unique. If the `is_active` flag is being changed, the system records `activated_at` (when switching from inactive to active) or `deactivated_at` (when switching from active to inactive). The currently authenticated admin's ID is stored in `updated_by`.

On success, the user is redirected to the branch detail page.

Required permission: `core.branches.edit`.

#### Delete a Branch (Soft Delete)

The user sends a delete request for a branch. The system performs a soft delete, setting the `deleted_at` timestamp without removing the database row. Soft-deleted branches no longer appear in normal listings but can be restored.

Required permission: `core.branches.delete`.

#### Restore a Soft-Deleted Branch

The user sends a restore request using the numeric branch ID. The system looks up the branch including soft-deleted records, verifies it is actually in a soft-deleted state, and calls restore. If the ID is not found or the branch is not deleted, the system returns an error flash message. On success, the branch becomes visible in the list again.

Required permission: `core.branches.edit`.

#### Activate or Deactivate a Branch (Toggle Status)

The user sends a toggle-status request for a branch. The system flips the `is_active` flag. If the branch was inactive and is now being activated, `activated_at` is set to now and `deactivated_at` is cleared. If the branch was active and is now being deactivated, `deactivated_at` is set to now. The admin who performed the toggle is recorded in `updated_by`.

The flash message reports whether the branch was "activated" or "deactivated" based on the state at the moment of reading the flag (note: the status message reads from the in-memory object before the update has refreshed it, so the message wording should be treated as informational rather than definitive).

Required permission: `core.branches.edit`.

#### Set a Branch as Main

The user sends a set-as-main request for a branch. The system first clears the `is_main` flag on every other branch in the table, then sets `is_main = true` on the target branch. There is always at most one main branch.

Required permission: `core.branches.edit`.

---

## Sub-Feature: Settings

### What Settings Are

Settings are key-value pairs stored in the database, grouped by functional area and scoped to either the whole system or a specific branch. They allow the application's behaviour to be configured without code changes.

The settings the UI currently exposes for editing are:

- `company_name_ar` — the company name in Arabic (group: company)
- `company_name_en` — the company name in English (group: company)
- `system_default_currency` — the default currency code, exactly 3 characters (e.g. SAR, USD) (group: system)
- `system_default_language` — the default locale code, exactly 2 characters (e.g. ar, en) (group: system)
- `sales_default_tax_rate` — the default VAT or sales tax percentage, numeric, 0–100 (group: sales)

Settings can optionally be stored in encrypted form (the `is_encrypted` flag). When a setting is encrypted, the value is transparently decrypted when retrieved. The current settings UI only manages unencrypted settings; encrypted settings would typically be seeded or managed programmatically.

Settings support type casting on read. The stored value is always a string in the database, but when retrieved through the SettingService, it is cast to the declared type: boolean, integer, float, JSON, or plain string.

### Actions a User Can Perform on Settings

#### View Settings

The user navigates to the settings page. The system renders a form pre-populated with the current setting values, ready to be edited in one operation.

Required permission: `core.settings.view`.

#### Update Settings

The user fills in the settings form and submits. The server validates all fields (see field rules above). For each validated field, the system parses the field name to derive the group prefix (the part before the first underscore) and the setting key (the remainder). For example, `company_name_en` produces group `company` and key `name_en`. The setting is then upserted (created if it does not exist, updated if it does), always at the `system` scope with no branch override.

On success, the user is redirected back to the settings page with a success message.

Required permission: `core.settings.update`.

---

## Sub-Feature: Number Sequences

### What Number Sequences Are

Number sequences are the counters the system uses to generate unique, human-readable document identifiers. Each sequence is defined by four identifying dimensions:

- Module (e.g. `sales`, `finance`, `inventory`, `hr`)
- Document type (e.g. `invoices`, `journal_entries`, `purchase_order`, `payroll`)
- Branch ID (null means company-wide, a branch ID means the sequence is isolated to that branch)
- Year (the calendar year; sequences reset at the start of each new year)

Each sequence record also carries a prefix (e.g. `INV-`, `PO-`) and a padding width (default 5), which together determine the format of the generated number. The counter starts at zero and increments with each issued number.

### How Numbering Works

When a document-creating service needs a number, it calls the sequence service to either retrieve or create the appropriate sequence record, then increment and format the counter. The resulting string looks like `INV-00001` or `00042` depending on whether a prefix is configured.

The system provides a "peek" operation that returns what the next number would be without incrementing the counter. This is useful for previewing a document number before committing the record.

The sequence counter is incremented at the database level (not application level) to guard against race conditions when two concurrent requests might otherwise receive the same number.

### Which Document Types Use Number Sequences

The following document types across the system consume number sequences, each tracked under its module and document key:

Sales module:
- Quotations (`sales` / `quotations`)
- Sales orders (`sales` / `sales_orders`)
- Invoices (`sales` / `invoices`)
- Sales payments (`sales` / `payments`)
- Sales returns (`sales` / `returns`)

Finance module:
- Journal entries (`finance` / `journal_entries`)
- Receipt vouchers (`finance` / `receipt_vouchers`)
- Payment vouchers (`finance` / `payment_vouchers`)
- Voucher reversals (uses the voucher number service for a new counter)

Inventory module:
- Purchase orders (`inventory` / `purchase_order`)
- Purchase receipts / goods receipt notes (`inventory` / `purchase_receipt`)
- Stock movements (`inventory` / `stock_movement`)

HR module:
- Payroll runs (`hr` / `payroll`)

Number sequences are managed entirely through code — there is no dedicated UI for browsing or editing sequences in the current implementation. Each module service calls `NumberSequenceService::getOrCreate()` at the time of document creation. Administrators who need to adjust a starting counter or prefix would do so at the database level or through a future admin UI.

### Sequence Scoping and Year Resets

A sequence with `branch_id = null` is company-wide: all branches share the same counter. A sequence with a specific `branch_id` is local to that branch. Year-based isolation means that once the calendar year changes, the next call to `getOrCreate()` will create a fresh sequence row starting from zero rather than continuing from the prior year's count.

The unique constraint on (module, document, branch_id, year) guarantees that no duplicate rows can exist, even under concurrent requests.
