> ## 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.

# FA-UX-05 / FW-03 Integration Contract

> Version: 1.0.0 Last Updated: 2026-01-28 Constitution Reference: Section 1.3 (Integration Patterns - Pattern 3: API Contracts) Status: 📝 Planned

**Version:** 1.0.0\
**Last Updated:** 2026-01-28\
**Constitution Reference:** Section 1.3 (Integration Patterns - Pattern 3: API Contracts)\
**Status:** 📝 Planned

***

## Overview

This document describes the integration contract between **FA-UX-05 (Purchase Order Creation Wizard)** and **FW-03 (Approval Workflows)** for submitting purchase orders for approval.

**Related Specifications:**

* FA-UX-05: Purchase Order Creation Wizard (`specs/fa/ux/FA-UX-05-purchase-order-creation-wizard.md`)
* FW-03: Approval Workflows (referenced as dependency in FA-UX-05)
* FA-04: Purchase Orders & Procurement (`specs/fa/specs/FA-04-purchase-orders.md`)

***

## Integration Pattern

**Pattern:** Pattern 3 - API Contracts (Synchronous Request-Response)

**Rationale:** Purchase order submission for approval requires synchronous confirmation that the approval request was created and the PO status was updated. This is a request-response interaction, not an event-driven workflow.

***

## API Contracts

### Submit Purchase Order for Approval

**Endpoint:** `/api/v1/fw/approval-requests/submit`\
**Method:** POST\
**Provider:** FW (Forms & Workflow) - FW-03 Approval Workflows\
**Consumer:** FA (Finance & Accounting) - FA-UX-05 Purchase Order Creation Wizard\
**Status:** 📝 Planned

#### Purpose

Submit a purchase order created via FA-UX-05 wizard for approval workflow processing. The approval workflow determines approvers based on PO amount, department, and organization approval chain configuration.

#### Request

**Endpoint:**

```
POST /api/v1/fw/approval-requests/submit
Authorization: Bearer {jwt_token}
Content-Type: application/json
```

**Request Body:**

```typescript theme={null}
interface SubmitPOApprovalRequest {
  organization_id: uuid;           // REQUIRED - Multi-tenant isolation
  source_type: 'purchase_order';   // REQUIRED - Identifies source entity type
  source_id: uuid;                 // REQUIRED - Purchase order ID (fa_purchase_orders.id)
  source_table: 'fa_purchase_orders'; // REQUIRED - Table name for entity type
  chain_id?: uuid;                 // OPTIONAL - Specific approval chain ID (if not provided, system selects based on rules)
  title: string;                   // REQUIRED - Approval request title (e.g., "PO-2026-001: Office Supplies")
  description?: string;            // OPTIONAL - Approval request description
  request_data: {                  // REQUIRED - Snapshot of PO data at time of submission
    po_number: string;
    vendor_id: uuid;
    vendor_name: string;
    total_amount: number;
    line_count: number;
    delivery_date?: string;        // ISO date
    ship_to_site_id?: uuid;
    department_id?: uuid;
    fund_id?: uuid;
    project_id?: uuid;
  };
  priority?: 'low' | 'normal' | 'high' | 'urgent'; // OPTIONAL - Default: 'normal'
  submitted_by: uuid;              // REQUIRED - User ID submitting the request
}
```

#### Response

**Success (201 Created):**

```typescript theme={null}
interface SubmitPOApprovalResponse {
  approval_request_id: uuid;       // Created approval request ID
  organization_id: uuid;
  chain_id: uuid;                  // Approval chain ID used
  current_step: number;            // Current approval step (starts at 1)
  status: 'pending' | 'in_progress'; // Initial status
  next_approver?: {                // Next approver (if known)
    user_id: uuid;
    user_name: string;
    role?: string;
  };
  estimated_completion?: string;   // ISO date - Estimated completion based on SLA
  submitted_at: string;           // ISO timestamp
}
```

**Error Response:**

```typescript theme={null}
interface ErrorResponse {
  error: {
    code: 'INVALID_REQUEST' | 'UNAUTHORIZED' | 'ACCESS_DENIED' | 'CHAIN_NOT_FOUND' | 'VALIDATION_ERROR' | 'SERVER_ERROR';
    message: string;                // Human-readable error message
    details?: {
      field?: string;               // Field name if validation error
      constraint?: string;          // Constraint violated
    };
  }
}
```

#### Error Codes

| Status | Code               | Description                                                                  |
| ------ | ------------------ | ---------------------------------------------------------------------------- |
| 400    | `INVALID_REQUEST`  | Invalid request parameters (missing required fields, invalid UUIDs)          |
| 401    | `UNAUTHORIZED`     | Missing or invalid JWT token                                                 |
| 403    | `ACCESS_DENIED`    | User does not have permission to submit approval requests                    |
| 404    | `CHAIN_NOT_FOUND`  | No approval chain configured for this PO type/amount                         |
| 422    | `VALIDATION_ERROR` | Business rule validation failed (e.g., PO already submitted, invalid status) |
| 429    | `RATE_LIMITED`     | Too many requests (rate limit exceeded)                                      |
| 500    | `SERVER_ERROR`     | Internal server error                                                        |

#### Security

* **Authentication:** JWT required in `Authorization: Bearer {token}` header
* **Organization Validation:** `organization_id` validated against user's accessible organizations
* **RLS Enforcement:** RLS policies enforce tenant isolation on approval requests
* **Rate Limiting:** 100 requests/minute per organization
* **Permission Check:** User must have `fw.approval.submit` permission (PF-30)

#### Usage Example

```typescript theme={null}
// In FA-UX-05 wizard completion handler
import { supabase } from '@/integrations/supabase/client';

async function submitPOForApproval(
  poId: string,
  organizationId: string,
  userId: string,
  poData: PurchaseOrderCreationWizardData
) {
  const { data, error } = await supabase.functions.invoke('fw-submit-approval', {
    body: {
      organization_id: organizationId,
      source_type: 'purchase_order',
      source_id: poId,
      source_table: 'fa_purchase_orders',
      title: `PO-${poData.po_number}: ${poData.vendor_name}`,
      description: `Purchase order for ${poData.line_items.length} line items`,
      request_data: {
        po_number: poData.po_number,
        vendor_id: poData.vendor_id,
        vendor_name: poData.vendor_name,
        total_amount: poData.total_amount,
        line_count: poData.line_items.length,
        delivery_date: poData.delivery_date,
        ship_to_site_id: poData.ship_to_site_id,
        department_id: poData.department_id,
        fund_id: poData.fund_id,
        project_id: poData.project_id,
      },
      priority: poData.total_amount > 10000 ? 'high' : 'normal',
      submitted_by: userId,
    },
  });

  if (error) {
    throw new Error(`Failed to submit for approval: ${error.message}`);
  }

  return data; // SubmitPOApprovalResponse
}
```

***

## Platform-Layer Usage

### Services/Hooks Calling FW-03

**Primary Hook:** `useSubmitPurchaseOrderApproval`

**Location:** `src/cores/fa/hooks/usePurchaseOrders.ts` (or similar)

**Implementation:**

```typescript theme={null}
import { supabase } from '@/integrations/supabase/client';
import { useMutation, useQueryClient } from '@tanstack/react-query';

export function useSubmitPurchaseOrderApproval(organizationId: string) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (params: {
      poId: string;
      poData: PurchaseOrderCreationWizardData;
    }) => {
      const { data: user } = await supabase.auth.getUser();
      if (!user.user) throw new Error('User not authenticated');

      const { data, error } = await supabase.functions.invoke('fw-submit-approval', {
        body: {
          organization_id: organizationId,
          source_type: 'purchase_order',
          source_id: params.poId,
          source_table: 'fa_purchase_orders',
          title: `PO-${params.poData.po_number}: ${params.poData.vendor_name}`,
          request_data: {
            po_number: params.poData.po_number,
            vendor_id: params.poData.vendor_id,
            vendor_name: params.poData.vendor_name,
            total_amount: params.poData.total_amount,
            line_count: params.poData.line_items.length,
            // ... other fields
          },
          submitted_by: user.user.id,
        },
      });

      if (error) throw error;
      return data;
    },
    onSuccess: () => {
      // Invalidate PO queries to refresh status
      queryClient.invalidateQueries({ queryKey: ['purchase-orders', organizationId] });
    },
  });
}
```

### Expected Payloads

**Request Payload Structure:**

* `organization_id`: UUID (required)
* `source_type`: `'purchase_order'` (required)
* `source_id`: UUID of `fa_purchase_orders.id` (required)
* `source_table`: `'fa_purchase_orders'` (required)
* `title`: String with PO number and vendor name (required)
* `request_data`: JSONB snapshot of PO data (required)
* `submitted_by`: UUID of submitting user (required)

**Response Payload Structure:**

* `approval_request_id`: UUID of created approval request
* `chain_id`: UUID of approval chain used
* `current_step`: Number (starts at 1)
* `status`: `'pending'` or `'in_progress'`
* `next_approver`: Optional approver information
* `submitted_at`: ISO timestamp

### Retry/Timeout Semantics

**Retry Policy:**

* **Max Retries:** 3 attempts
* **Retry Conditions:** Network errors (5xx), timeout errors
* **No Retry:** 4xx errors (client errors), validation errors
* **Backoff:** Exponential backoff (1s, 2s, 4s)

**Timeout:**

* **Request Timeout:** 30 seconds
* **Connection Timeout:** 10 seconds

**Error Handling:**

* Network errors: Retry with exponential backoff
* Validation errors: Show user-friendly error message, allow correction
* Permission errors: Show access denied message, redirect if needed
* Server errors: Show generic error, log for support

***

## Sequencing and Workflow State Transitions

### Purchase Order Status Transitions

**Before Submission:**

* `status: 'draft'` - PO created but not submitted

**On Submission (via FW-03):**

* `status: 'pending_approval'` - PO submitted, awaiting approval
* `approval_request_id` stored in `fa_purchase_orders.approval_request_id`

**During Approval:**

* `status: 'pending_approval'` - Remains until approval/rejection
* Approval status tracked in `fw_approval_requests.status`

**After Approval:**

* `status: 'approved'` - PO approved, ready for goods receipt
* `approved_at` timestamp set
* `approved_by` user ID set

**After Rejection:**

* `status: 'rejected'` - PO rejected, cannot proceed
* `rejected_at` timestamp set
* `rejected_by` user ID set
* `rejection_reason` stored

### Approval Workflow State Transitions

**Request Creation (FA-UX-05 → FW-03):**

1. FA-UX-05 wizard completes, creates PO with `status: 'draft'`
2. FA-UX-05 calls FW-03 API to submit for approval
3. FW-03 creates `fw_approval_requests` record with `status: 'pending'`
4. FW-03 determines approval chain based on PO amount, department, organization rules
5. FW-03 assigns first approver(s) based on chain configuration
6. FW-03 updates PO status to `'pending_approval'` and sets `approval_request_id`
7. FW-03 publishes `approval_request_created` event
8. FW-03 sends notification to first approver(s) via PF-10

**Approval Processing (FW-03 Internal):**

1. Approver reviews and approves/rejects
2. If approved and more steps: Move to next step, notify next approver
3. If approved and final step: Complete approval, publish `approval_request_approved` event
4. If rejected: Complete rejection, publish `approval_request_rejected` event

**Approval Completion (FW-03 → FA):**

1. FW-03 publishes `approval_request_approved` or `approval_request_rejected` event
2. FA event handler receives event
3. FA updates PO status to `'approved'` or `'rejected'`
4. FA updates `approved_at`/`rejected_at` and `approved_by`/`rejected_by`
5. FA sends notification to PO creator via PF-10

### Event Contracts

**Event: `approval_request_created`**

**Publisher:** FW-03 (Approval Workflows)\
**Subscribers:** PF-10 (Notifications), Event Consumers

**Payload:**

```typescript theme={null}
interface ApprovalRequestCreatedPayload {
  event_type: 'approval_request_created';
  organization_id: uuid;
  timestamp: string; // ISO timestamp
  approval_request_id: uuid;
  source_type: 'purchase_order';
  source_id: uuid; // PO ID
  submitted_by: uuid;
  next_approver?: uuid;
}
```

**Event: `approval_request_approved`**

**Publisher:** FW-03 (Approval Workflows)\
**Subscribers:** FA (Purchase Orders), PF-10 (Notifications)

**Payload:**

```typescript theme={null}
interface ApprovalRequestApprovedPayload {
  event_type: 'approval_request_approved';
  organization_id: uuid;
  timestamp: string; // ISO timestamp
  approval_request_id: uuid;
  source_type: 'purchase_order';
  source_id: uuid; // PO ID
  approved_by: uuid;
  completed_at: string; // ISO timestamp
}
```

**Event: `approval_request_rejected`**

**Publisher:** FW-03 (Approval Workflows)\
**Subscribers:** FA (Purchase Orders), PF-10 (Notifications)

**Payload:**

```typescript theme={null}
interface ApprovalRequestRejectedPayload {
  event_type: 'approval_request_rejected';
  organization_id: uuid;
  timestamp: string; // ISO timestamp
  approval_request_id: uuid;
  source_type: 'purchase_order';
  source_id: uuid; // PO ID
  rejected_by: uuid;
  rejection_reason?: string;
  completed_at: string; // ISO timestamp
}
```

***

## Integration Examples

### Example 1: Submit PO for Approval

```typescript theme={null}
// In FA-UX-05 wizard completion handler
const { mutate: submitForApproval, isLoading } = useSubmitPurchaseOrderApproval(organizationId);

// After PO creation
const poId = await createPurchaseOrder(poData);

// Submit for approval
submitForApproval(
  {
    poId,
    poData,
  },
  {
    onSuccess: (response) => {
      // Update PO with approval request ID
      updatePurchaseOrder(poId, {
        approval_request_id: response.approval_request_id,
        status: 'pending_approval',
      });
      
      // Show success message
      toast.success('Purchase order submitted for approval');
      
      // Navigate to PO detail page
      navigate(`/fa/purchase-orders/${poId}`);
    },
    onError: (error) => {
      // Show error message
      toast.error(`Failed to submit for approval: ${sanitizeErrorMessage(error)}`);
    },
  }
);
```

### Example 2: Handle Approval Event

```typescript theme={null}
// In FA event handler (edge function or client subscription)
supabase
  .channel('approval_events')
  .on('postgres_changes', {
    event: 'INSERT',
    schema: 'public',
    table: 'fw_approval_events',
    filter: `event_type=eq.approval_request_approved,source_type=eq.purchase_order`,
  }, (payload) => {
    const event = payload.new as ApprovalRequestApprovedPayload;
    
    // Update PO status
    await supabase
      .from('fa_purchase_orders')
      .update({
        status: 'approved',
        approved_at: event.completed_at,
        approved_by: event.approved_by,
      })
      .eq('id', event.source_id)
      .eq('organization_id', event.organization_id);
    
    // Send notification to PO creator
    await sendNotification({
      user_id: poData.created_by,
      type: 'po_approved',
      title: 'Purchase Order Approved',
      message: `PO ${poData.po_number} has been approved`,
    });
  })
  .subscribe();
```

***

## Testing Requirements

### Unit Tests

* [ ] Request payload validation
* [ ] Response payload parsing
* [ ] Error handling (all error codes)
* [ ] Retry logic (network errors, timeouts)
* [ ] Organization validation

### Integration Tests

* [ ] Submit PO for approval (happy path)
* [ ] Approval chain selection based on PO amount
* [ ] PO status update on submission
* [ ] Event publishing on approval/rejection
* [ ] PO status update on approval completion

### E2E Tests

* [ ] Complete wizard flow → Submit for approval → Approval workflow → PO status update
* [ ] Error scenarios (invalid PO, missing chain, permission denied)
* [ ] Retry scenarios (network failure, timeout)

***

## Related Documentation

* **FA-UX-05 Spec:** `specs/fa/ux/FA-UX-05-purchase-order-creation-wizard.md`
* **FA-04 Spec:** `specs/fa/specs/FA-04-purchase-orders.md`
* **FW-03/FW-34 Spec:** `specs/fw/specs/FW-34-approval-workflows.md` (or FW-03 if different)
* **Event Contracts:** `docs/architecture/integrations/EVENT_CONTRACTS.md`
* **API Contracts:** `docs/architecture/integrations/API_CONTRACTS.md`
* **Integration Matrix:** `docs/architecture/integrations/CROSS_CORE_INTEGRATIONS.md`

***

**Last Updated:** 2026-01-28\
**Next Review:** After FW-03/FW-34 implementation
