# Sales Module — Business Flows

## Overview

The Sales module covers the complete order-to-cash cycle: managing customers, issuing price quotations, confirming sales orders, raising invoices, recording customer payments, and handling returns. A separate Setup sub-section holds tax rate configuration. Five report views let staff monitor revenue, outstanding balances, and payment history.

All sales documents are scoped to a branch. Every document number (quotation, order, invoice, payment, return) is generated by the shared `NumberSequenceService`, which maintains a per-module/per-type/per-branch/per-year counter with a configurable prefix and zero-padding width.

---

## Customer Management

### What a Customer Record Contains

A customer has a unique alphanumeric code, a translatable name and address (stored as JSON to support multiple locales), and optional contact details — phone and email. The record also carries a tax registration number used on printed documents.

Financial configuration on the customer record includes:

- **Credit limit** — a decimal ceiling on outstanding balance.
- **Payment terms days** — the default number of days before a raised invoice is due.
- **AR account** — the specific Accounts Receivable ledger account that GL entries for this customer will post to. If left blank, the system falls back to the system-wide default AR account configured in Settings.
- **Opening balance** and **opening balance date** — used to bring in a pre-existing balance when migrating from another system.
- **Currency code** — the customer's default billing currency (three-letter ISO code).

### Creating and Editing Customers

Any admin user with the `sales.customers.create` permission can open the customer creation form. The form offers a branch selector (restricting the customer to one branch) and an account picker (filtered to postable, active GL accounts). On save the record is immediately active.

Editing follows the same form. A soft delete is used rather than a hard delete, so a customer's history is always preserved even after deactivation.

### Customer Statement

A dedicated statement view, accessible from the customer list, is provided by the Finance AR/AP report controller. It shows all invoices, payments, and return credits for a selected customer in chronological order.

---

## Sales Tax Rates

### Configuration

Tax rates are defined in the Setup area before any sales document can carry tax. Each rate record has:

- A unique code and a translatable name.
- A **rate** (percentage, stored to four decimal places) applied to the taxable line amount.
- A **tax payable account** — the GL account that receives the tax credit when an invoice is posted.
- Flags for **active** and **default**. Only one rate can be marked as the system default; setting a new default automatically clears the flag on all others.

### How Rates Are Applied

Tax is applied line-by-line. When a user assigns a tax rate to a line on a quotation, order, or invoice, the service calculates: first the line subtotal (quantity × unit price), then a line-level discount reduction, and finally the tax amount as a percentage of the after-discount amount. Tax is rounded to two decimal places per line.

No cascading or compound tax logic exists; each line carries at most one rate.

---

## Quotations

### Purpose and Lifecycle

A quotation is an offer to a customer that has not yet been agreed. It passes through the following statuses:

1. **Draft** — the quotation can be freely edited or deleted.
2. **Sent** — the quotation has been formally issued to the customer. Editing is locked.
3. **Accepted** — the customer has agreed to the terms.
4. **Rejected** — the customer has declined.
5. **Expired** — detected automatically when the expiry date has passed while the quotation is still in Sent status.

### Creating a Quotation

The user selects a customer, a branch, a quotation date, and an optional expiry date. One or more line items are added, each referencing an active inventory item with quantity and unit price. No tax or discount is captured at the quotation stage; line totals are simply quantity × price. The total amount is the sum of all line totals.

A quotation number is generated automatically on save (e.g., QT-0001 for the first quotation of the year for that branch).

### Sending, Accepting, and Rejecting

- **Send** moves the status from Draft to Sent. Only draft quotations can be sent.
- **Accept** moves the status from Sent to Accepted. Only sent quotations can be accepted.
- **Reject** moves the status from Sent to Rejected. Only sent quotations can be rejected.

All three transitions are one-way and cannot be undone.

### Converting to a Sales Order

An accepted quotation can be converted to a sales order in a single click. The system creates a new sales order in Draft status, copies all line items (item, description, quantity, unit price) from the quotation, and links the resulting order back to the quotation via the `converted_to_order_id` field. The quotation is updated to record which order it became.

---

## Sales Orders

### Purpose

A sales order is a confirmed commitment to sell specific items to a customer. Orders can be created either by converting an accepted quotation or from scratch (direct order).

### Creating an Order

The creation form asks for customer, branch, order date, and an optional delivery date. If the order is being created manually rather than from a quotation, the user may optionally link it to an existing quotation for reference.

Each line item carries: item, optional description, quantity, unit price, an optional line-level discount (as a percentage), and an optional tax rate. The service computes:

- `line_subtotal` = quantity × unit_price
- `discount_amount` = line_subtotal × (discount_percent / 100)
- `tax_amount` = (line_subtotal − discount_amount) × (tax_rate.rate / 100)
- `line_total` = (line_subtotal − discount_amount) + tax_amount

At the order header level, an additional discount can be applied either as a fixed amount or a percentage of the subtotal. The header-level discount is summed with line discounts to arrive at `discount_amount` on the order. The final `total_amount` is subtotal minus the header discount plus the sum of all line taxes.

### Status Transitions

- **Draft** — editable and deletable.
- **Confirmed** — the order is firm. The `confirmed_at` timestamp is recorded. Editing is locked. Only confirmed orders can be cancelled.
- **Cancelled** — the order will not proceed. A cancelled order cannot be reinstated.

Only draft orders can be deleted; the delete operation removes all lines first inside a transaction.

---

## Invoices

### Purpose and Relationship to Orders

An invoice is the billing document sent to the customer. It can be linked to a sales order (to track how much of the order has been invoiced) or created independently. There is no strict one-to-one constraint; multiple invoices may reference the same order.

### Creating an Invoice

The invoice form requires: customer, branch, invoice date, and optionally a due date (which, if left blank, the system leaves null rather than defaulting to the customer's payment terms — due-date defaulting is left to the UI layer). An optional currency code and exchange rate can be supplied for foreign-currency billing.

Line configuration mirrors the sales order: item, description, quantity, unit price, line discount percentage, and tax rate. The same arithmetic applies — subtotal, discount, tax, and line total computed per line. At the invoice level the header totals are:

- `subtotal` = sum of all `line_subtotal` values
- `tax_amount` = sum of all line `tax_amount` values
- `total_amount` = subtotal − sum of line discount amounts + tax_amount

### Invoice Statuses

- **Draft** — editable and deletable. Payment tracking is not active.
- **Confirmed** — the invoice is issued. Editing is locked. This status makes the invoice eligible for payment recording and for posting to the GL.
- **Posted** — the invoice has been posted to the General Ledger (see Finance Integration below). The `posted_at` timestamp and a `journal_entry_id` are recorded.
- **Cancelled** — the invoice is voided. Cancellation is allowed from any status except posted (the service enforces this with a guard check).

### Payment Tracking

Two columns on the invoice track cash receipts: `paid_amount` (cumulative total of all payments applied) and `payment_status`, which is one of:

- **unpaid** — no payments have been applied.
- **partially_paid** — some payment has been received but the balance remains positive.
- **paid** — `paid_amount` equals or exceeds `total_amount`.

These columns are updated automatically each time a sales payment is posted or reversed.

### PDF Export

A PDF of any invoice can be downloaded via a dedicated route. The PDF is generated using DomPDF and renders the invoice header, customer details, line items, and totals.

### Finance Integration (GL Posting)

Posting an invoice creates a balanced journal entry in the sales journal:

- **Debit** Accounts Receivable for the full invoice total.
- **Credit** Revenue account(s) for the net line amounts (after line discounts), resolved per item or per item category, falling back to a system-wide default revenue account.
- **Credit** Tax Payable account(s) for the tax amount on each line, resolved per tax rate record, falling back to a system-wide default.
- For inventory-type items, the posting service additionally **Debits** COGS and **Credits** the Inventory account for the weighted-average cost of goods shipped, and records a stock movement of type "out".

Posting requires that a `sales_journal_id` setting is configured. The branch and an open fiscal period for the invoice date are also required; if either is missing the post action fails with a descriptive error message.

---

## Sales Payments

### Recording a Payment

A payment records cash received from a customer. The user selects the customer, branch, payment date, amount, payment method (cash, bank transfer, cheque, or other), and an optional external reference number.

The key step is **invoice allocation**: the user sees a list of that customer's unpaid and partially-paid confirmed invoices, each showing its total amount, already-paid amount, and current balance. The user enters the amount to apply against each invoice. The sum of allocations need not equal the payment amount (allowing overpayments or under-allocations), though the UI typically guides the user to match them.

The allocation is stored in the `sales_payment_invoices` pivot table with an `applied_amount` per invoice.

### Posting a Payment

Until posted, a payment is in Draft status and has no effect on invoice balances. When posted:

1. Each invoice in the allocation has its `paid_amount` incremented by the `applied_amount`.
2. Each invoice's `payment_status` is recalculated: `paid` if `paid_amount >= total_amount`, `partially_paid` if `paid_amount > 0`, or `unpaid` otherwise.
3. The payment moves to Posted status.

### Reversing a Payment

A posted payment can be reversed. Reversal:

1. Reduces each linked invoice's `paid_amount` by the originally applied amount (floored at zero).
2. Recalculates each invoice's `payment_status`.
3. Creates a new payment record with a negative amount (the reversal) linked back to the original via `reversed_from_id`. The reversal is immediately in Posted status.
4. Marks the original payment as Reversed.

Only draft payments can be deleted.

---

## Sales Returns

### Purpose

A sales return records goods that a customer sends back after an invoice has been raised. Every return must reference an original confirmed invoice.

### Creating a Return

The creation form starts with a customer selection, which loads that customer's confirmed invoices via an Ajax call. Once an invoice is selected, a second Ajax call loads its individual lines (item, quantity, unit price, tax amount). The user picks which lines to return and sets the quantity for each.

Additional fields include: return date, branch, reason (free text), and whether the returned goods are to be restocked (put back into inventory). If restocked, a warehouse must be selected.

### Stock Impact

When a return is posted and `restocked` is true, for each line where the item type is "stock":

1. A stock movement of type "in" is created in the selected warehouse, using the item's current weighted-average cost.
2. The stock balance service updates the item's on-hand quantity and average cost.
3. The COGS amount for the line is recorded (quantity × average cost at the time of posting).

### GL Impact

The posting service creates the following journal entry when the sales journal is configured:

- **Debit** Revenue account(s) for the return line net amounts (reversing the original revenue recognition).
- **Debit** Tax Payable account(s) for the tax (reversing the original tax credit).
- **Credit** Accounts Receivable for the full return total (reducing what the customer owes).
- For restocked stock items: **Debit** Inventory account and **Credit** COGS account for the restocked cost.

### Invoice Balance Adjustment

After posting, the original invoice's `paid_amount` is increased by the return's `total_amount`. This treats the return credit as equivalent to a payment, which may push the invoice's `payment_status` from unpaid to partially_paid or paid.

### Quantity Validation

Before posting, the service verifies that the return quantity for each line does not exceed the quantity still available to return. The system looks up how much of that original invoice line has already been returned on other posted returns and subtracts that from the original quantity.

---

## Sales Reports

The reports section provides five views, all within the `sales/reports` prefix.

### Sales Summary Report (`/`)

The main report shows all confirmed invoices, filterable by branch, customer, and date range. Each row displays invoice number, date, customer, branch, subtotal, tax amount, and total amount. Aggregate totals for invoice count, subtotal, tax, and total are shown at the bottom.

### Receivables Aging Report (`/aging`)

Shows all confirmed, not-yet-fully-paid invoices, grouped into aging buckets based on days past the due date:

- **Current** — not yet overdue.
- **1–30 days** overdue.
- **31–60 days** overdue.
- **61–90 days** overdue.
- **Over 90 days** overdue.

Each row shows the customer, invoice reference, invoice and due dates, total amount, paid amount, outstanding balance, and days overdue. The report is filterable by customer and branch.

### Outstanding Invoices Report (`/outstanding`)

A paginated list of all confirmed invoices that are not fully paid. Filterable by customer, branch, and invoice date range. Useful for collections follow-up.

### Invoice Register (`/invoice-register`)

A complete list of invoices across all statuses (draft, confirmed, posted, cancelled), filterable by customer, branch, status, and date range. Each row shows the invoice number, dates, customer, branch, subtotal, tax, total, paid amount, remaining balance, payment status, and document status.

### Payment Register (`/payment-register`)

A list of all posted sales payments, filterable by customer, branch, and payment date range. Each row shows payment number, date, customer, branch, amount, payment method, and external reference.
