> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Revenue Recognition (Advanced) — Integration

> Spec ID: FA-18 Status: 📋 Contract frozen (implementation 📝 Planned — no production RPC until migrations ship) Last Updated: 2026-04-26

**Spec ID:** FA-18\
**Status:** 📋 Contract frozen (implementation 📝 Planned — no production RPC until migrations ship)\
**Last Updated:** 2026-04-26

Canonical integration reference for FA-18: revenue contracts, revenue schedules (**invoice XOR contract** source), deferred revenue, **single fiscal period** recognition runs, and GL posting via FA-02.

**Authoritative duplicates:** Event payload is defined identically in [EVENT\_CONTRACTS.md](./EVENT_CONTRACTS.md) § FA-18. HTTP shape (if used) is defined in [API\_CONTRACTS.md](./API_CONTRACTS.md) § FA-18. This file ties them together and documents GL line mapping and transport choice.

***

## Integration Summary

| From  | To       | Pattern     | Description                                                                                                                       |
| ----- | -------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- |
| FA-18 | FA-02    | RPC / event | Balanced JEs on recognition; `fa_revenue_recognitions.journal_entry_id` + `journal_entry_line_id` (revenue line for traceability) |
| FA-18 | FA-05    | FK / read   | `fa_revenue_schedules.invoice_id` → `fa_invoices`                                                                                 |
| FA-18 | FA-13    | FK / read   | `fa_revenue_contracts.project_id` → `fa_projects`                                                                                 |
| FA-18 | FA-01    | FK          | `fa_revenue_recognitions.fiscal_period_id` → `fa_fiscal_periods`                                                                  |
| FA-18 | PF-01    | Context     | `organization_id` on all tenant tables                                                                                            |
| FA-18 | FA-UX-15 | UX delegate | Schedule creation wizard; same DDL and server validation (no parallel tables)                                                     |

***

## Transport (frozen)

| Path                                                                                                                                           | When to use                                                                                                                                                      |
| ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Primary:** `public.fa_process_revenue_recognition(p_organization_id uuid, p_recognition_date date, p_fiscal_period_id uuid, p_user_id uuid)` | Server-side period run; **SECURITY DEFINER**; enforces RLS context; one fiscal period per call.                                                                  |
| **Optional HTTP:** `POST /api/v1/fa/revenue/recognize`                                                                                         | Thin wrapper (Edge Function or API route) that validates `fa.revenue_recognitions.process`, maps body → RPC args, returns JSON matching **HTTP response** below. |

Callers must hold **PF-30** `fa.revenue_recognitions.process` (or equivalent) in addition to tenant RLS.

***

## RPC: `fa_process_revenue_recognition`

**Signature (frozen):**

```sql theme={null}
fa_process_revenue_recognition(
  p_organization_id uuid,
  p_recognition_date date,
  p_fiscal_period_id uuid,
  p_user_id uuid
) RETURNS TABLE(
  schedule_id uuid,
  recognition_amount numeric,
  journal_entry_id uuid
);
```

**Semantics:**

1. Select **active** `fa_revenue_schedules` for `p_organization_id` that intersect the fiscal period identified by `p_fiscal_period_id` (via `fa_fiscal_periods`), have `remaining_to_recognize > 0`, and satisfy date window rules per spec.
2. For each schedule, insert **at most one** `fa_revenue_recognitions` row for `(revenue_schedule_id, fiscal_period_id)` (UNIQUE).
3. Update `fa_revenue_schedules.total_recognized` in the same transaction as the insert.
4. **Phase 1 GL scope (DEFERRED):** Do **not** call `fa_create_journal_entry()` from `fa_process_revenue_recognition()` in Phase 1. Recognition rows are written with `journal_entry_id IS NULL` and `journal_entry_line_id IS NULL`. A Phase 2 task will post GL via FA-02 and back-fill the linkage. This boundary keeps Phase 1 self-contained and decoupled from FA-02's not-yet-frozen RPC signature.
5. Inserts MUST use `ON CONFLICT (revenue_schedule_id, fiscal_period_id) DO NOTHING` so the function is idempotent on retry.

**Optional filter (HTTP only):** If the HTTP layer accepts `schedule_ids?: uuid[]`, it MUST filter to those IDs **within** `p_organization_id` only; omitting the array means “all due schedules” for the period (same as RPC-only callers invoking for the whole org period batch).

***

## Event: `revenue_recognized`

**Publisher:** FA (FA-18)\
**Subscribers (planned):** FA-02, FA-05, FA-13

Emit **after** a recognition row is committed (and ideally after GL post when GL is in scope for that run).

**Payload (frozen)** — see [EVENT\_CONTRACTS.md](./EVENT_CONTRACTS.md):

```typescript theme={null}
{
  event_type: 'revenue_recognized';
  organization_id: uuid;
  recognition_id: uuid;
  revenue_schedule_id: uuid;
  fiscal_period_id: uuid;
  recognition_amount: number;
  recognition_date: date; // processing date
  recognition_period_start: date;
  recognition_period_end: date;
  journal_entry_id?: uuid;
  journal_entry_line_id?: uuid;
}
```

***

## HTTP: Recognize revenue (optional wrapper)

**Endpoint:** `POST /api/v1/fa/revenue/recognize`\
**Logical mapping:** Body → `fa_process_revenue_recognition` with `p_user_id = auth.uid()`.

**Request:**

```typescript theme={null}
{
  recognition_date: string; // ISO date
  fiscal_period_id: string;
  schedule_ids?: string[]; // optional filter; same-org only
}
```

**Response (200):**

```typescript theme={null}
{
  recognition_count: number;
  total_recognized: number;
  recognitions: Array<{
    recognition_id: string;
    revenue_schedule_id: string;
    recognition_amount: number;
    journal_entry_id?: string;
    journal_entry_line_id?: string;
  }>;
}
```

**Errors:** `400` invalid body / unknown period; `403` tenant or permission; `422` schedule not eligible (closed period, zero remaining, etc.). Do not echo internal SQL.

***

## GL mapping (FA-02)

| Schedule source                     | Debit (examples)                               | Credit  |
| ----------------------------------- | ---------------------------------------------- | ------- |
| Invoice-linked (`invoice_id` set)   | Accounts receivable (or org default per FA-01) | Revenue |
| Linked to deferred revenue workflow | Deferred revenue liability                     | Revenue |

Exact account resolution: **FA-01** CoA + org policy; implementation calls FA-02’s standard journal entry API/RPC. **`journal_entry_line_id`** SHOULD reference the **credit revenue line** (or documented pair) for audit trail per FA-18 spec clarifications.

***

## Planned consumers (Phase 2)

* `invoice_sent` (FA-05), `contract_signed` (FA-13) — auto-create schedules/contracts; not in Phase 1.

***

## Spec reference

* [FA-18 spec](../../../specs/fa/specs/FA-18-revenue-recognition-advanced.md)
* [FA-18 plan](../../../specs/fa/plans/FA-18-revenue-recognition-advanced-PLAN.md)

**PENDING\_CONTRACTS:** FA-18 integration IOU **resolved** 2026-04-26 — row archived under [Resolved Stubs](./PENDING_CONTRACTS.md#resolved-stubs).
