Now I have a comprehensive understanding of the entire codebase. Let me compose the implementation plan.

---

# Implementation Plan: NPHIES eClaims Integration for Moon ERP LIS

## Overview

This plan details the implementation of five frontend screens that integrate Saudi Arabia's NPHIES (National Platform for Health Information Exchange) eClaims platform into the existing Moon ERP Laboratory Information System. The backend APIs are already built and tested. All UI is gated behind a `nphies_enabled` feature flag -- when disabled, zero NPHIES code renders. The design follows the existing LIS design language: dark blue (#1a237e) + gold (#ffc107), Arabic-first RTL, standalone components, PrimeNG 21, signals-based state.

## Requirements

- Feature flag `nphies_enabled` controls all NPHIES UI visibility (fetched from `GET /api/nphies/config`)
- Arabic-first RTL layout with English toggle
- Consistent with existing LIS design patterns (stat-cards, form-sections, PrimeNG table/dialog)
- Every API call must handle loading, success, error, and timeout states
- All new components are standalone, lazy-loaded
- No NgRx for NPHIES -- use service + signals pattern (consistent with invoices, kanban, referrals)
- All new translation keys under `LIS.NPHIES.*` namespace

## Architecture Changes

### New Files to Create

| File | Purpose |
|------|---------|
| `src/app/core/services/nphies.service.ts` | Central NPHIES API service (config, eligibility, preauth, claim, transactions, poll, cancel) |
| `src/app/core/models/nphies.model.ts` | TypeScript interfaces for all NPHIES entities |
| `src/app/features/lis/nphies-settings/nphies-settings.component.ts` | Settings page (connection + code mapping) |
| `src/app/features/lis/nphies-settings/nphies-settings.component.html` | Settings template |
| `src/app/features/lis/nphies-settings/nphies-settings.component.scss` | Settings styles |
| `src/app/features/lis/nphies-log/nphies-log.component.ts` | Transaction log page |
| `src/app/features/lis/nphies-log/nphies-log.component.html` | Log template |
| `src/app/features/lis/nphies-log/nphies-log.component.scss` | Log styles |

### Existing Files to Modify

| File | Change |
|------|--------|
| `src/app/core/models/lis-patient.model.ts` | Add `national_id_type`, `passport_country` fields |
| `src/app/core/models/lis-invoice.model.ts` | Add NPHIES claim fields (`nphies_claim_status`, `nphies_preauth_ref`, `nphies_covered_amount`, `nphies_copay_amount`) |
| `src/app/features/lis/lis-standalone.routes.ts` | Add routes for `/lab/settings/nphies` and `/lab/nphies-log` |
| `src/app/features/lis/lis-layout/lis-layout.component.ts` | Add NPHIES nav items (conditional on feature flag) |
| `src/app/features/lis/patients/lis-patients.component.ts` | Add Saudi ID type selector, conditional fields |
| `src/app/features/lis/patients/lis-patients.component.html` | Add NPHIES form fields |
| `src/app/features/lis/request-wizard-v2/request-wizard-v2.component.ts` | Add eligibility check when insurance selected |
| `src/app/features/lis/request-wizard-v2/request-wizard-v2.component.html` | Add eligibility banner |
| `src/app/features/lis/invoices/lis-invoices.component.ts` | Add preauth/claim buttons and NPHIES columns |
| `src/app/features/lis/invoices/lis-invoices.component.html` | Add NPHIES table columns and action dialogs |
| `src/app/features/lis/dashboard/lis-dashboard.component.ts` | Add NPHIES KPI card |
| `src/app/features/lis/dashboard/lis-dashboard.component.html` | Add NPHIES stat card |
| `src/assets/i18n/ar.json` | Add all `LIS.NPHIES.*` Arabic translations |
| `src/assets/i18n/en.json` | Add all `LIS.NPHIES.*` English translations |

---

## Implementation Steps

### Phase 0: Foundation (Service + Models + Feature Flag)

#### Step 0.1: Create NPHIES Models

**File**: `src/app/core/models/nphies.model.ts`

**Action**: Define all TypeScript interfaces for the NPHIES domain.

```typescript
// Interfaces to define:

export type NphiesIdType = 'national-id' | 'iqama' | 'passport' | 'visa' | 'border-number';

export type NphiesTransactionType = 'eligibility' | 'preauth' | 'claim' | 'cancel' | 'poll';

export type NphiesTransactionStatus = 'sent' | 'success' | 'error' | 'timeout';

export type NphiesClaimStatus = 'pending' | 'authorized' | 'claimed' | 'rejected' | 'paid';

export interface NphiesConfig {
  provider_license: string;
  facility_nphies_id: string;
  sandbox_mode: boolean;
  certificate_path: string | null;
  key_path: string | null;
  nphies_enabled: boolean;
}

export interface NphiesEligibilityRequest {
  patient_id: number;
  insurer_code?: string;
  service_date?: string;
}

export interface NphiesEligibilityResponse {
  eligible: boolean;
  payer_name: string | null;
  plan: string | null;
  network: string | null;
  member_id: string | null;
  coverage_amount: number | null;
  disposition: string | null;
  raw_response?: any;
}

export interface NphiesPreAuthRequest {
  invoice_id: number;
  investigation_ids?: number[];
}

export interface NphiesPreAuthResponse {
  preauth_ref: string;
  status: 'approved' | 'denied' | 'pended';
  valid_from: string | null;
  valid_to: string | null;
  approved_amount: number;
  items: NphiesAdjudicationItem[];
  disposition: string | null;
  raw_response?: any;
}

export interface NphiesClaimRequest {
  invoice_id: number;
  preauth_ref?: string;
}

export interface NphiesClaimResponse {
  claim_id: string;
  status: 'complete' | 'error' | 'partial';
  total_submitted: number;
  total_benefit: number;
  total_copay: number;
  payment_date: string | null;
  items: NphiesAdjudicationItem[];
  disposition: string | null;
  raw_response?: any;
}

export interface NphiesAdjudicationItem {
  sequence: number;
  investigation_name?: string;
  submitted: number;
  eligible: number;
  benefit: number;
  copay: number;
}

export interface NphiesTransaction {
  id: number;
  type: NphiesTransactionType;
  bundle_id: string;
  status: NphiesTransactionStatus;
  patient_id: number | null;
  patient?: { id: number; name: string; name_ar: string; mrn: string };
  lab_request_id: number | null;
  request_json: any;
  response_json: any;
  error_message: string | null;
  created_at: string;
  updated_at: string;
}
```

**Why**: Type safety for all NPHIES API interactions. Defined early so all consumers can import.
**Dependencies**: None.
**Risk**: Low. Pure type definitions.
**Complexity**: Low.

---

#### Step 0.2: Create NPHIES Service

**File**: `src/app/core/services/nphies.service.ts`

**Action**: Create the central `NphiesService` following the existing service pattern.

Methods:
- `getConfig(): Observable<ApiResponse<NphiesConfig>>`
- `updateConfig(data: Partial<NphiesConfig>): Observable<ApiResponse<NphiesConfig>>`
- `testConnection(): Observable<ApiResponse<{ success: boolean; message: string }>>`
- `uploadCertificate(file: File, type: 'cert' | 'key'): Observable<ApiResponse<any>>`
- `checkEligibility(data: NphiesEligibilityRequest): Observable<ApiResponse<NphiesEligibilityResponse>>`
- `requestPreAuth(data: NphiesPreAuthRequest): Observable<ApiResponse<NphiesPreAuthResponse>>`
- `submitClaim(data: NphiesClaimRequest): Observable<ApiResponse<NphiesClaimResponse>>`
- `cancelTransaction(transactionId: number): Observable<ApiResponse<any>>`
- `pollPending(): Observable<ApiResponse<any>>`
- `getTransactions(page: number, perPage: number, filters?: Record<string, string>): Observable<PaginatedResponse<NphiesTransaction>>`
- `getTransaction(id: number): Observable<ApiResponse<NphiesTransaction>>`

The service also exposes a shared `nphiesEnabled` signal that is loaded once and cached:

```typescript
readonly nphiesEnabled = signal(false);
private configLoaded = false;

loadFeatureFlag(): void {
  if (this.configLoaded) return;
  this.getConfig().subscribe({
    next: (res) => {
      this.nphiesEnabled.set(res.data.nphies_enabled);
      this.configLoaded = true;
    },
    error: () => this.nphiesEnabled.set(false),
  });
}
```

**API base**: `${environment.apiUrl}/nphies`

**Why**: Single service for all NPHIES API interactions. The shared `nphiesEnabled` signal is the authoritative source of the feature flag, injectable anywhere.
**Dependencies**: Step 0.1 (models).
**Risk**: Low. Standard service pattern identical to 65+ existing services.
**Complexity**: Low-Medium.

---

#### Step 0.3: Update Patient Model

**File**: `src/app/core/models/lis-patient.model.ts`

**Action**: Add three fields to `LisPatient` and `CreateLisPatient`:

On `LisPatient`:
```typescript
national_id_type: NphiesIdType | null;   // new
passport_country: string | null;          // new
```

On `CreateLisPatient`:
```typescript
national_id_type?: NphiesIdType;   // new
passport_country?: string;         // new
```

**Why**: Backend already has these columns (from task #1462). Frontend model must match.
**Dependencies**: Step 0.1.
**Risk**: Low. Adding optional fields is backward-compatible.

---

#### Step 0.4: Update Invoice Model

**File**: `src/app/core/models/lis-invoice.model.ts`

**Action**: Add NPHIES fields to `LisInvoice`:

```typescript
nphies_claim_status: NphiesClaimStatus | null;
nphies_preauth_ref: string | null;
nphies_covered_amount: string | null;
nphies_copay_amount: string | null;
```

**Why**: Backend already has these columns (from task #1466). Frontend must render them.
**Dependencies**: Step 0.1.
**Risk**: Low.

---

#### Step 0.5: Initialize Feature Flag in LIS Layout

**File**: `src/app/features/lis/lis-layout/lis-layout.component.ts`

**Action**: Inject `NphiesService` and call `loadFeatureFlag()` in `ngOnInit()`. This makes `nphiesService.nphiesEnabled()` available to all child components via injection.

Add two conditional nav items:
1. In the `fin` (Financial) nav group: add `{ label: 'NAV.LIS_NPHIES_LOG', icon: 'pi pi-history', route: '/lab/nphies-log' }` -- conditionally pushed only when `nphiesService.nphiesEnabled()` is true.
2. In a new `settings` nav group (or existing if one exists): add `{ label: 'NAV.LIS_NPHIES_SETTINGS', icon: 'pi pi-sliders-h', route: '/lab/settings/nphies' }`.

The nav group array should be converted to a computed signal so it reacts to the feature flag:

```typescript
private nphiesService = inject(NphiesService);

navGroups = computed<NavGroup[]>(() => {
  const base = [...this.baseNavGroups];
  if (this.nphiesService.nphiesEnabled()) {
    // Inject NPHIES items into Financial group
    const fin = base.find(g => g.id === 'fin');
    if (fin) {
      fin.items = [...fin.items, { label: 'NAV.LIS_NPHIES_LOG', icon: 'pi pi-history', route: '/lab/nphies-log' }];
    }
    // Add settings item
    base.push({
      id: 'nphies',
      label: 'LIS.NAV.NPHIES',
      icon: 'pi pi-shield',
      items: [
        { label: 'NAV.LIS_NPHIES_SETTINGS', icon: 'pi pi-sliders-h', route: '/lab/settings/nphies' },
        { label: 'NAV.LIS_NPHIES_LOG', icon: 'pi pi-history', route: '/lab/nphies-log' },
      ],
    });
  }
  return base;
});
```

**Why**: Central feature flag loading. All child components can inject `NphiesService` and check `nphiesEnabled()` without additional API calls.
**Dependencies**: Step 0.2.
**Risk**: Medium. Must not break existing navigation. The nav groups array is currently a static property; converting to a computed signal requires updating the template to call `navGroups()` instead of `navGroups`.
**Complexity**: Medium.

---

#### Step 0.6: Add Routes

**File**: `src/app/features/lis/lis-standalone.routes.ts`

**Action**: Add two new route entries in the children array:

```typescript
{
  path: 'settings/nphies',
  loadComponent: () =>
    import('./nphies-settings/nphies-settings.component').then(
      (m) => m.NphiesSettingsComponent
    ),
},
{
  path: 'nphies-log',
  loadComponent: () =>
    import('./nphies-log/nphies-log.component').then(
      (m) => m.NphiesLogComponent
    ),
},
```

Also update the breadcrumb map in `lis-layout.component.ts` to include:
```typescript
'settings': 'LIS.NAV.SETTINGS',
'nphies': 'LIS.NPHIES.TITLE',
'nphies-log': 'NAV.LIS_NPHIES_LOG',
```

**Why**: Lazy-loaded routes following existing pattern.
**Dependencies**: None (components do not need to exist yet for route definition).
**Risk**: Low.

---

#### Step 0.7: Add Translation Keys

**Files**: `src/assets/i18n/ar.json`, `src/assets/i18n/en.json`

**Action**: Add all translation keys needed across the 5 screens. The complete list:

```json
// Arabic (ar.json) — inside "LIS" object
"NPHIES": {
  "TITLE": "NPHIES التأمين الصحي",
  "SETTINGS": "إعدادات NPHIES",
  "TRANSACTION_LOG": "سجل المعاملات",
  "ENABLED": "NPHIES مفعّل",
  "DISABLED": "NPHIES معطّل",

  "CONFIG": {
    "PROVIDER_LICENSE": "ترخيص مقدم الخدمة",
    "FACILITY_ID": "معرّف المنشأة في NPHIES",
    "SANDBOX_MODE": "الوضع التجريبي (Sandbox)",
    "SANDBOX_HINT": "تفعيل الاتصال بالبيئة التجريبية بدلاً من الإنتاجية",
    "CERTIFICATE": "شهادة الأمان (PEM)",
    "PRIVATE_KEY": "المفتاح الخاص (PEM)",
    "UPLOAD_CERT": "رفع الشهادة",
    "UPLOAD_KEY": "رفع المفتاح",
    "TEST_CONNECTION": "اختبار الاتصال",
    "TESTING": "جارٍ اختبار الاتصال...",
    "CONNECTION_OK": "الاتصال ناجح",
    "CONNECTION_FAIL": "فشل الاتصال",
    "SAVE_CONFIG": "حفظ الإعدادات",
    "CERT_UPLOADED": "تم رفع الشهادة بنجاح",
    "KEY_UPLOADED": "تم رفع المفتاح بنجاح"
  },

  "CODE_MAPPING": {
    "TITLE": "ربط أكواد SBSCS",
    "INVESTIGATION": "الفحص",
    "SBSCS_CODE": "كود SBSCS",
    "LOINC_CODE": "كود LOINC",
    "NO_CODE": "غير مربوط",
    "SAVE": "حفظ الأكواد",
    "UNMAPPED_COUNT": "{{count}} فحص بدون كود"
  },

  "ELIGIBILITY": {
    "CHECKING": "جارٍ التحقق من التغطية التأمينية...",
    "ELIGIBLE": "مغطّى",
    "NOT_ELIGIBLE": "غير مغطّى",
    "PAYER": "شركة التأمين",
    "PLAN": "الخطة",
    "NETWORK": "الشبكة",
    "MEMBER_ID": "رقم العضوية",
    "COVERAGE_AMOUNT": "مبلغ التغطية",
    "OVERRIDE": "متابعة بدون NPHIES",
    "RETRY": "إعادة التحقق",
    "ERROR": "خطأ في التحقق من التغطية",
    "TIMEOUT": "انتهت مهلة الاتصال بـ NPHIES"
  },

  "PREAUTH": {
    "REQUEST": "طلب موافقة مسبقة",
    "REQUESTING": "جارٍ إرسال طلب الموافقة...",
    "APPROVED": "معتمد",
    "DENIED": "مرفوض",
    "PENDED": "قيد المراجعة",
    "REF": "رقم الموافقة",
    "VALID_FROM": "صالح من",
    "VALID_TO": "صالح حتى",
    "APPROVED_AMOUNT": "المبلغ المعتمد",
    "SELECT_TESTS": "اختر الفحوصات لطلب الموافقة"
  },

  "CLAIM": {
    "SUBMIT": "إرسال مطالبة",
    "SUBMITTING": "جارٍ إرسال المطالبة...",
    "SUBMITTED": "تم إرسال المطالبة",
    "COVERED": "المبلغ المغطّى",
    "COPAY": "حصة المريض",
    "TOTAL_SUBMITTED": "المبلغ المقدّم",
    "TOTAL_BENEFIT": "المبلغ المعتمد",
    "PAYMENT_DATE": "تاريخ الدفع المتوقع",
    "VIEW_FHIR": "عرض FHIR Bundle"
  },

  "STATUS": {
    "PENDING": "معلّق",
    "AUTHORIZED": "معتمد",
    "CLAIMED": "تم المطالبة",
    "REJECTED": "مرفوض",
    "PAID": "مدفوع",
    "SENT": "مرسل",
    "SUCCESS": "ناجح",
    "ERROR": "خطأ",
    "TIMEOUT": "انتهت المهلة"
  },

  "LOG": {
    "TITLE": "سجل معاملات NPHIES",
    "TYPE": "نوع المعاملة",
    "BUNDLE_ID": "معرّف الحزمة",
    "DATE": "التاريخ",
    "PATIENT": "المريض",
    "REQUEST": "الطلب",
    "STATUS": "الحالة",
    "VIEW_JSON": "عرض JSON",
    "REQUEST_JSON": "طلب FHIR",
    "RESPONSE_JSON": "استجابة FHIR",
    "NO_TRANSACTIONS": "لا توجد معاملات",
    "FILTER_TYPE": "تصفية حسب النوع",
    "FILTER_STATUS": "تصفية حسب الحالة",
    "FILTER_DATE": "تصفية حسب التاريخ",
    "EXPORT": "تصدير"
  },

  "DASHBOARD": {
    "TITLE": "NPHIES اليوم",
    "ELIGIBILITY_COUNT": "تحقق تأمين",
    "PREAUTH_COUNT": "موافقة مسبقة",
    "CLAIM_COUNT": "مطالبة مرسلة",
    "COVERED_AMOUNT": "مبلغ مغطّى"
  },

  "PATIENT": {
    "ID_TYPE": "نوع الهوية",
    "NATIONAL_ID": "الهوية الوطنية",
    "IQAMA": "الإقامة",
    "PASSPORT": "جواز السفر",
    "VISA": "تأشيرة",
    "BORDER_NUMBER": "رقم حدود",
    "ID_NUMBER": "رقم الهوية",
    "PASSPORT_COUNTRY": "بلد جواز السفر",
    "ID_REQUIRED": "رقم الهوية مطلوب عند تفعيل NPHIES",
    "NATIONAL_ID_HINT": "10 أرقام، يبدأ بـ 1",
    "IQAMA_HINT": "10 أرقام، يبدأ بـ 2"
  }
}
```

English translations follow the same key structure with English values.

**Why**: All text must be translatable. Arabic is the primary language.
**Dependencies**: None.
**Risk**: Low.

---

### Phase 1: Screen 1 -- NPHIES Settings Page

**Route**: `/lab/settings/nphies`
**File**: `src/app/features/lis/nphies-settings/nphies-settings.component.ts`
**Task**: #1468

#### 1.1 User Story

**Who**: Lab administrator / IT manager.
**When**: During initial NPHIES setup, or when changing connection details, or when mapping SBSCS codes to investigations.
**Why**: To configure the connection to Saudi Arabia's NPHIES eClaims platform, upload security certificates, verify connectivity, and map billing codes.

#### 1.2 UI Layout

```
┌─────────────────────────────────────────────────────────────────────────┐
│ PageHeader: "إعدادات NPHIES" [حفظ الإعدادات]                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ ┌─── Tab 1: الاتصال ──────────────────────────────────────────────────┐ │
│ │                                                                      │ │
│ │ ┌─ Connection Card ──────────────────────────────────────────────┐   │ │
│ │ │  Provider License:  [________________]                         │   │ │
│ │ │  Facility NPHIES ID: [________________]                        │   │ │
│ │ │  Sandbox Mode:      [toggle switch]  "البيئة التجريبية"       │   │ │
│ │ └────────────────────────────────────────────────────────────────┘   │ │
│ │                                                                      │ │
│ │ ┌─ Certificate Card ─────────────────────────────────────────────┐   │ │
│ │ │  Certificate (PEM):  [cert.pem ✓]  [رفع شهادة جديدة]         │   │ │
│ │ │  Private Key (PEM):  [key.pem ✓]   [رفع مفتاح جديد]          │   │ │
│ │ └────────────────────────────────────────────────────────────────┘   │ │
│ │                                                                      │ │
│ │ ┌─ Test Connection Card ─────────────────────────────────────────┐   │ │
│ │ │  [🔗 اختبار الاتصال]                                          │   │ │
│ │ │                                                                │   │ │
│ │ │  ✓ الاتصال ناجح — Sandbox (2026-04-02 14:23)                  │   │ │
│ │ │  OR                                                            │   │ │
│ │ │  ✗ فشل الاتصال: Certificate expired                           │   │ │
│ │ └────────────────────────────────────────────────────────────────┘   │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│ ┌─── Tab 2: ربط الأكواد ──────────────────────────────────────────────┐ │
│ │                                                                      │ │
│ │ ┌─ Summary Banner ───────────────────────────────────────────────┐   │ │
│ │ │  ⚠ 15 فحص بدون كود SBSCS من أصل 120                          │   │ │
│ │ └────────────────────────────────────────────────────────────────┘   │ │
│ │                                                                      │ │
│ │ ┌─ Search ───────────────────────────────────────────────────────┐   │ │
│ │ │  [🔍 البحث...] [تصفية: الكل | بدون كود | مربوط]              │   │ │
│ │ └────────────────────────────────────────────────────────────────┘   │ │
│ │                                                                      │ │
│ │ ┌─ Table ────────────────────────────────────────────────────────┐   │ │
│ │ │  الفحص      │ الكود   │ SBSCS           │ LOINC     │ الحالة │   │ │
│ │ │─────────────┼─────────┼─────────────────┼───────────┼────────│   │ │
│ │ │  CBC        │ HEM001  │ [920010101    ] │ 57021-8   │ ✓      │   │ │
│ │ │  TSH        │ HOR005  │ [___________  ] │ 11580-8   │ ⚠      │   │ │
│ │ │  ...        │         │                 │           │        │   │ │
│ │ └────────────────────────────────────────────────────────────────┘   │ │
│ │                                                                      │ │
│ │ [حفظ الأكواد]                                                        │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```

#### 1.3 UX Flow

1. Page loads. Skeleton placeholder shown (400px height, matching existing pattern).
2. Parallel API calls: `GET /api/nphies/config` and investigation listAll. Both complete before rendering.
3. **Tab 1 -- Connection**: Form fields pre-populated from config. Sandbox toggle with gold-on-dark-blue styling. Certificate upload uses native file input styled as PrimeNG button. Upload immediately calls `POST /api/nphies/config/upload-certificate` with `FormData`. On success: green check icon next to filename. On error: red cross with error detail.
4. **Test Connection button**: Calls `POST /api/nphies/config/test-connection`. While loading, button shows spinner and text "جارٍ اختبار الاتصال...". On success: green banner with checkmark, timestamp, and mode (Sandbox/Production). On error: red banner with error message and retry suggestion. The result persists on screen until another test.
5. **Save Config**: Calls `PUT /api/nphies/config`. Standard toast notification on success/error.
6. **Tab 2 -- Code Mapping**: Table loads all investigations. SBSCS column has inline-editable `p-inputText` (click to focus, Tab to next row). Filter dropdown: All / Unmapped / Mapped. Search filters by investigation name or code. Unmapped rows show warning icon in Status column. Bulk save calls the investigations update endpoint for each changed row (parallel, batched in groups of 10).
7. **Unmapped counter**: Orange banner at top: "15 فحص بدون كود SBSCS من أصل 120". Updates in real-time as user fills in codes.

#### 1.4 Visual Design

- **Page background**: `#f8fafc` (matching LIS pages)
- **Cards**: White background, `border-radius: 16px`, `border: 1px solid rgba(0,0,0,0.05)`, `box-shadow: 0 1px 3px rgba(0,0,0,0.04)`
- **Card headers**: `font-size: 0.9rem`, `font-weight: 700`, `color: #334155`, icon in gold (#ffc107) inset-inline-start of text
- **Tab bar**: PrimeNG `TabView` with two tabs, gold underline for active tab
- **Sandbox toggle**: PrimeNG `ToggleSwitch`. When ON, show amber badge "تجريبي" next to it
- **Test connection result**: Inline `p-message` component. `severity="success"` for OK, `severity="error"` for fail. Includes timestamp.
- **Certificate status**: Green pill badge "cert.pem ✓" when uploaded, grey "غير مرفوع" when missing
- **Code mapping table**: `p-table` with `scrollable`, `scrollHeight="450px"`. SBSCS input: `width: 140px`, monospace font, `text-align: center`. Status column: green checkmark (`pi pi-check-circle`, `color: #10b981`) or amber warning (`pi pi-exclamation-triangle`, `color: #f59e0b`).
- **Unmapped banner**: `background: #fef3c7`, `border-inline-start: 4px solid #f59e0b`, `border-radius: 8px`, `padding: 12px 16px`

#### 1.5 Component Architecture

```typescript
@Component({
  selector: 'app-nphies-settings',
  standalone: true,
  imports: [ /* PrimeNG modules, TranslateModule, etc. */ ],
  providers: [MessageService],
})
export class NphiesSettingsComponent implements OnInit {
  private nphiesService = inject(NphiesService);
  private investigationService = inject(LisInvestigationService);
  private messageService = inject(MessageService);
  lang = inject(LanguageService);

  // State signals
  loading = signal(true);
  saving = signal(false);
  testingConnection = signal(false);
  connectionResult = signal<{ success: boolean; message: string; timestamp: string } | null>(null);
  uploadingCert = signal(false);
  uploadingKey = signal(false);
  activeTab = signal(0);

  // Config form
  config = signal<NphiesConfig | null>(null);
  providerLicense = signal('');
  facilityId = signal('');
  sandboxMode = signal(true);
  certUploaded = signal(false);
  keyUploaded = signal(false);

  // Code mapping
  investigations = signal<LisInvestigation[]>([]);
  codeChanges = signal<Map<number, string>>(new Map());
  codeFilter = signal<'all' | 'unmapped' | 'mapped'>('all');
  codeSearch = signal('');
  savingCodes = signal(false);

  // Computed
  unmappedCount = computed(() => /* ... */);
  filteredInvestigations = computed(() => /* ... */);
  hasUnsavedChanges = computed(() => this.codeChanges().size > 0);
}
```

#### 1.6 RTL/Arabic Considerations

- All labels Arabic-first; English shown in parentheses for technical terms: "ترخيص مقدم الخدمة (Provider License)"
- SBSCS codes are numeric, rendered LTR even in RTL context using `direction: ltr; text-align: center` on the input
- Certificate filenames rendered LTR: `dir="ltr"` on filename display
- Tab bar respects RTL (active indicator slides from right)

#### 1.7 Responsive Behavior

- **Desktop (>1024px)**: Connection card fields in 2-column grid; code mapping table full width
- **Tablet (768-1024px)**: Single column for connection fields; table horizontal scroll
- **Mobile (<768px)**: Stacked layout; tabs switch to accordion-style; table shows essential columns only (name, SBSCS, status)

#### 1.8 Accessibility

- All form inputs have explicit `<label>` elements with `for` attribute
- Tab navigation works with arrow keys (PrimeNG built-in)
- File upload button has `aria-label="Upload PEM certificate file"`
- Connection test result announced via `aria-live="polite"` region
- Contrast ratios: all text meets WCAG AA (4.5:1 minimum)

#### 1.9 Edge Cases

- **Config not yet created** (first visit): API returns defaults. All fields empty. Show info banner: "قم بإدخال بيانات الاتصال لتفعيل NPHIES"
- **Certificate upload fails** (wrong format): Show inline error "يجب أن يكون الملف بصيغة PEM"
- **Test connection timeout** (>10s): Show "انتهت مهلة الاتصال — تحقق من الشبكة والشهادة"
- **No investigations loaded**: Show empty state in code mapping tab
- **User navigates away with unsaved changes**: No blocker needed (low risk, codes are not critical path)
- **API 403** (no permission): Show permission denied message, disable save button

---

### Phase 2: Screen 2 -- Patient Saudi ID Fields

**File**: `src/app/features/lis/patients/lis-patients.component.ts` + `.html`
**Task**: #1469

#### 2.1 User Story

**Who**: Lab receptionist registering a new patient.
**When**: During patient creation or edit, when NPHIES is enabled.
**Why**: NPHIES requires a Saudi identifier (National ID, Iqama, Passport, or Visa number) for every patient to check insurance eligibility.

#### 2.2 UI Layout

The existing patient form dialog currently has fields: name_ar, name, date_of_birth, gender, national_id, blood_group, phone, email, address, address_ar, medical_history, insurance_contract_id.

**New fields inserted after `national_id`** (wrapped in `@if (nphiesEnabled())`):

```
┌─── Existing Patient Dialog ──────────────────────────────────────┐
│                                                                    │
│  الاسم عربي: [________]   الاسم انجليزي: [________]              │
│  تاريخ الميلاد: [____]    الجنس: [ذكر ▾]                         │
│                                                                    │
│  ┌─── NPHIES Section (only when enabled) ─────────────────────┐   │
│  │  نوع الهوية: [الهوية الوطنية ▾]                             │   │
│  │  رقم الهوية: [1234567890_____]  "10 أرقام، يبدأ بـ 1"      │   │
│  │                                                              │   │
│  │  (if passport selected):                                     │   │
│  │  بلد جواز السفر: [المملكة المتحدة ▾]                        │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                                                                    │
│  فصيلة الدم: [____]  الهاتف: [________]                          │
│  ...                                                               │
└────────────────────────────────────────────────────────────────────┘
```

**Badge in patient table** (new column after national_id, only when NPHIES enabled):

```
│ ... │ نوع الهوية │ رقم الهوية   │ ...
│ ... │ 🇸🇦 وطنية  │ 1234567890  │ ...
│ ... │ 📋 إقامة   │ 2098765432  │ ...
│ ... │ ✈ جواز     │ AB123456   │ ...
```

#### 2.3 UX Flow

1. When NPHIES is enabled, the form shows a visually distinct section with a subtle blue-gray background and a shield icon header.
2. **ID Type dropdown**: 5 options. Defaults to "الهوية الوطنية" (National ID). Changing the type resets the ID number field and updates validation.
3. **ID Number input**: Real-time validation as user types.
   - National ID: Exactly 10 digits, starts with `1`. Regex: `/^1\d{9}$/`
   - Iqama: Exactly 10 digits, starts with `2`. Regex: `/^2\d{9}$/`
   - Passport: Alphanumeric, 5-20 characters. Shows "بلد جواز السفر" dropdown.
   - Visa: Alphanumeric, 5-20 characters.
   - Border Number: Numeric, 10 digits.
4. **Validation hint text**: Below the input in muted text. Changes based on selected type. Red when invalid, muted when neutral.
5. **Required when NPHIES enabled**: The `national_id` field becomes `Validators.required`. If user tries to save without it, the field highlights red with "رقم الهوية مطلوب عند تفعيل NPHIES".
6. **Passport country**: Only visible when type is `passport`. PrimeNG `Select` with country list from `src/assets/data/countries.json` (already exists in the app for other features).
7. **Patient table**: New column showing ID type as a colored tag badge. Only renders when NPHIES enabled.
8. **Patient chip in wizard**: The patient selection chip in the request wizard should show the ID type badge when NPHIES is enabled.

#### 2.4 Visual Design

- **NPHIES section wrapper**: `background: #f0f4ff` (very light blue), `border-radius: 12px`, `padding: 16px`, `border: 1px solid #e0e7ff`, `margin: 8px 0`
- **Section header**: `font-size: 0.8rem`, `font-weight: 600`, `color: #475569`, icon: `pi pi-shield` in `color: #1a237e`
- **ID type badges**: Colored tag badges in the table:
  - National ID: `background: #dbeafe`, `color: #1d4ed8`, text: "وطنية"
  - Iqama: `background: #d1fae5`, `color: #047857`, text: "إقامة"
  - Passport: `background: #fef3c7`, `color: #b45309`, text: "جواز"
  - Visa: `background: #f3e8ff`, `color: #7c3aed`, text: "تأشيرة"
  - Border: `background: #fee2e2`, `color: #b91c1c`, text: "حدود"
- **Validation hint**: `font-size: 0.7rem`, `margin-top: 4px`, normal: `color: #94a3b8`, error: `color: #ef4444`
- **ID number input**: `font-family: 'JetBrains Mono', monospace` (fallback: `monospace`), `direction: ltr`, `text-align: start`, `letter-spacing: 1px`

#### 2.5 Component Architecture

Add to existing `LisPatientsComponent`:

```typescript
// New signals
nphiesEnabled = computed(() => this.nphiesService.nphiesEnabled());

idTypeOptions = [
  { label: 'LIS.NPHIES.PATIENT.NATIONAL_ID', value: 'national-id' },
  { label: 'LIS.NPHIES.PATIENT.IQAMA', value: 'iqama' },
  { label: 'LIS.NPHIES.PATIENT.PASSPORT', value: 'passport' },
  { label: 'LIS.NPHIES.PATIENT.VISA', value: 'visa' },
  { label: 'LIS.NPHIES.PATIENT.BORDER_NUMBER', value: 'border-number' },
];

countries = signal<{ label: string; value: string }[]>([]);
showPassportCountry = computed(() => this.form?.get('national_id_type')?.value === 'passport');
```

Modify `initForm()` to add:
```typescript
national_id_type: ['national-id'],
passport_country: [null],
```

Add a custom validator on the `national_id` field that reads the `national_id_type` value:
```typescript
private idNumberValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const type = this.form?.get('national_id_type')?.value;
    const val = control.value;
    if (!val) return this.nphiesService.nphiesEnabled() ? { required: true } : null;
    switch (type) {
      case 'national-id': return /^1\d{9}$/.test(val) ? null : { pattern: 'NATIONAL_ID_HINT' };
      case 'iqama': return /^2\d{9}$/.test(val) ? null : { pattern: 'IQAMA_HINT' };
      case 'passport': return val.length >= 5 && val.length <= 20 ? null : { minlength: true };
      default: return null;
    }
  };
}
```

Also modify `onSave()` to include the new fields in the payload.

#### 2.6 RTL/Arabic Considerations

- ID number input is always LTR (numbers read left-to-right even in Arabic)
- Country dropdown labels are bilingual: "المملكة المتحدة (GB)" format
- Validation messages are in Arabic
- Tag badges in the table respect RTL layout (badge on the right side of the cell)

#### 2.7 Responsive Behavior

- On desktop: ID type and ID number side by side in a 2-column row
- On mobile: Stacked vertically
- Passport country appears as full-width row below when visible

#### 2.8 Accessibility

- ID type dropdown has `aria-label`
- Validation errors linked via `aria-describedby`
- Tag badges have `aria-label` for screen readers (e.g., "نوع الهوية: الهوية الوطنية")

#### 2.9 Edge Cases

- **Existing patient with no ID type**: Default to `null`. When NPHIES enabled and editing, prompt to fill it
- **Switching ID type clears ID number**: Prevents invalid data carryover
- **NPHIES disabled after patient created with ID fields**: Fields hidden, data preserved in DB
- **Duplicate national ID**: Backend validates uniqueness. Show error from 422 response
- **Quick add dialog**: Also needs the ID fields when NPHIES enabled (but minimal -- just type + number)

---

### Phase 3: Screen 3 -- Eligibility Check in Request Wizard Step 3

**File**: `src/app/features/lis/request-wizard-v2/request-wizard-v2.component.ts` + `.html`
**Task**: #1470

#### 3.1 User Story

**Who**: Lab receptionist creating a new lab request.
**When**: In the request wizard, after selecting a patient with insurance and choosing insurance as the billing type.
**Why**: To verify in real-time that the patient's insurance covers the ordered tests before proceeding, avoiding claim rejections later.

#### 3.2 UI Layout

The eligibility banner inserts into the existing wizard layout, in the billing section (between the insurance contract dropdown and the financial summary):

```
┌─── Request Wizard V2 ────────────────────────────────────────────────┐
│                                                                        │
│  [Patient Section]  [Test Selection]                                   │
│                                                                        │
│  ── Billing Section ──                                                 │
│  Client Type: [فرد | تأمين ✓ | مختبر خارجي]                          │
│  Insurance Contract: [التعاونية — Gold ▾]                              │
│                                                                        │
│  ┌─── NPHIES Eligibility Banner ───────────────────────────────────┐   │
│  │                                                                  │   │
│  │  STATE A: Checking                                               │   │
│  │  ⟳ جارٍ التحقق من التغطية التأمينية...                         │   │
│  │  ███████░░░ (animated bar)                                       │   │
│  │                                                                  │   │
│  │  STATE B: Eligible                                               │   │
│  │  ✓ مغطّى                                                        │   │
│  │  التعاونية │ Gold-A │ VIP │ العضوية: MEM-001                    │   │
│  │  التغطية: 500,000 ر.س                                          │   │
│  │                                                                  │   │
│  │  STATE C: Not Eligible                                           │   │
│  │  ✗ غير مغطّى                                                    │   │
│  │  "Patient coverage not found or inactive"                        │   │
│  │  [إعادة التحقق] [متابعة بدون NPHIES]                           │   │
│  │                                                                  │   │
│  │  STATE D: Error / Timeout                                        │   │
│  │  ⚠ خطأ في التحقق — انتهت مهلة الاتصال                          │   │
│  │  [إعادة التحقق] [متابعة بدون NPHIES]                           │   │
│  │                                                                  │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                        │
│  ── Financial Summary ──                                               │
│  ...                                                                   │
└────────────────────────────────────────────────────────────────────────┘
```

#### 3.3 UX Flow

1. **Auto-trigger**: When `nphiesEnabled()` is true AND user selects insurance contract AND patient has a `national_id`, the eligibility check fires automatically. No manual trigger needed.
2. **Trigger conditions**:
   - Patient changes (new patient selected with insurance) -> trigger
   - Insurance contract changes -> trigger
   - Debounce: 500ms after the last trigger to avoid rapid re-checks
3. **Loading state**: Animated shimmer bar with "جارٍ التحقق من التغطية التأمينية..." text. Duration typically 1-3 seconds.
4. **Success (Eligible)**: Green banner. Shows payer name, plan, network, member ID, coverage amount. The banner has a subtle green glow effect. The information persists and scrolls with the form.
5. **Success (Not Eligible)**: Red banner. Shows disposition message from API. Two action buttons: "إعادة التحقق" (retry) and "متابعة بدون NPHIES" (override). Override sets a flag `eligibilityOverridden = true` that allows the wizard to proceed.
6. **Error/Timeout**: Amber banner. Shows error message. Same two action buttons. The wizard can still proceed (eligibility is not a hard gate -- it is informational).
7. **Override behavior**: When user clicks "متابعة بدون NPHIES", the banner collapses to a single-line notice: "⚠ تم تجاوز التحقق من التغطية" in muted amber text. The request proceeds without NPHIES data.
8. **Request submission**: If eligible, the eligibility response data is stored on the request (backend handles this). The `insurer_code` from the eligibility response is passed along.

#### 3.4 Visual Design

- **Banner container**: `border-radius: 12px`, `padding: 16px 20px`, `transition: all 0.3s ease`
- **Checking state**: `background: #f0f4ff`, `border: 1px solid #bfdbfe`. Shimmer animation on a progress bar (CSS keyframes, `width: 60%`, animated left-right).
- **Eligible state**: `background: linear-gradient(135deg, #ecfdf5, #d1fae5)`, `border: 1px solid #6ee7b7`. Checkmark icon: `color: #059669`, `font-size: 1.5rem`. Payer/plan/network shown as inline pill badges with `background: white`, `border-radius: 20px`, `padding: 4px 12px`, `font-size: 0.75rem`.
- **Not eligible state**: `background: linear-gradient(135deg, #fef2f2, #fee2e2)`, `border: 1px solid #fca5a5`. X icon: `color: #dc2626`.
- **Error state**: `background: #fffbeb`, `border: 1px solid #fde68a`. Warning icon: `color: #f59e0b`.
- **Override notice**: `background: transparent`, `color: #92400e`, `font-size: 0.75rem`, `font-style: italic`
- **Transition**: Banner height animates smoothly when switching between states (use CSS `max-height` transition or Angular `@trigger` animation)
- **Coverage amount**: Displayed in bold, with SAR suffix: `font-weight: 800`, `color: #047857`, `font-size: 1.1rem`

#### 3.5 Component Architecture

Add to existing `RequestWizardV2Component`:

```typescript
// New signals
eligibilityState = signal<'idle' | 'checking' | 'eligible' | 'not-eligible' | 'error'>('idle');
eligibilityData = signal<NphiesEligibilityResponse | null>(null);
eligibilityError = signal<string | null>(null);
eligibilityOverridden = signal(false);
private eligibilityTrigger$ = new Subject<void>();

// New computed
showEligibilityBanner = computed(() => {
  return this.nphiesService.nphiesEnabled()
    && this.selectedInsContractId() !== null
    && this.selectedPatient()?.national_id;
});
```

In `ngOnInit()`, set up the debounced trigger:
```typescript
this.eligibilityTrigger$.pipe(
  debounceTime(500),
  takeUntil(this.destroy$),
).subscribe(() => this.checkEligibility());
```

Watch for changes to patient and insurance contract that should trigger a check:
```typescript
// Called when patient or insurance contract changes
private triggerEligibilityIfNeeded(): void {
  if (!this.showEligibilityBanner()) {
    this.eligibilityState.set('idle');
    return;
  }
  this.eligibilityOverridden.set(false);
  this.eligibilityTrigger$.next();
}

private checkEligibility(): void {
  const patient = this.selectedPatient();
  if (!patient?.national_id) return;

  this.eligibilityState.set('checking');
  this.eligibilityData.set(null);
  this.eligibilityError.set(null);

  this.nphiesService.checkEligibility({
    patient_id: patient.id,
    service_date: new Date().toISOString().split('T')[0],
  }).subscribe({
    next: (res) => {
      this.eligibilityData.set(res.data);
      this.eligibilityState.set(res.data.eligible ? 'eligible' : 'not-eligible');
    },
    error: (err) => {
      this.eligibilityError.set(err.error?.message || 'Connection error');
      this.eligibilityState.set('error');
    },
  });
}

overrideEligibility(): void {
  this.eligibilityOverridden.set(true);
  this.eligibilityState.set('idle');
}

retryEligibility(): void {
  this.eligibilityTrigger$.next();
}
```

#### 3.6 RTL/Arabic Considerations

- Coverage amount: "500,000 ر.س" -- currency symbol after number in Arabic
- Member ID is alphanumeric, rendered LTR inside a `dir="ltr"` span
- Pill badges flow inline-end to inline-start in RTL

#### 3.7 Responsive Behavior

- Banner is full-width on all breakpoints
- On mobile, pill badges stack vertically instead of inline
- Coverage amount stays prominent at all sizes

#### 3.8 Accessibility

- Banner has `role="status"` and `aria-live="polite"` so screen readers announce changes
- Loading state uses `aria-busy="true"`
- Override button clearly labeled with action description
- Color is not the only indicator -- icons and text supplement every color-coded state

#### 3.9 Edge Cases

- **Patient has no national_id**: Banner does not appear. Eligibility section shows small hint: "يجب إدخال رقم الهوية للتحقق من التغطية"
- **Insurance contract not selected**: Banner does not appear
- **NPHIES backend returns slowly (>5s)**: Show "قد يستغرق التحقق وقتاً أطول..." after 5s. Timeout at 15s with error state.
- **Patient changed while eligibility is in-flight**: Previous request is ignored (use `switchMap` pattern -- cancel previous on new trigger)
- **Network offline**: Error state with clear message "لا يوجد اتصال بالإنترنت"
- **Patient changes from insured to uninsured (removes insurance contract)**: Banner disappears, eligibility state resets to idle

---

### Phase 4: Screen 4 -- PreAuth + Claim on Invoices

**File**: `src/app/features/lis/invoices/lis-invoices.component.ts` + `.html`
**Task**: #1471

#### 4.1 User Story

**Who**: Lab billing clerk / receptionist.
**When**: After an invoice is created (for insured patients), to request prior authorization before testing, and to submit the claim after results are released.
**Why**: NPHIES requires prior authorization for certain tests and claim submission after service delivery. This is how the lab gets paid by insurance.

#### 4.2 UI Layout

**New columns added to the invoice table** (only when NPHIES enabled):

```
│ Invoice# │ Patient │ Date │ Type │ Status │ Total │ NPHIES Status │ Covered │ Copay │ Actions │
│ INV-001  │ أحمد    │ 04/02 │ تأمين │ مرسل  │ 165   │ 🟡 معلّق      │ -       │ -     │ [⚡][📤][👁]│
│ INV-002  │ فاطمة   │ 04/02 │ تأمين │ مرسل  │ 220   │ 🔵 معتمد PA#  │ -       │ -     │ [📤][👁] │
│ INV-003  │ خالد    │ 04/01 │ تأمين │ مدفوع │ 300   │ 🟢 مطالبة     │ 240     │ 60    │ [👁]    │
│ INV-004  │ سارة    │ 04/01 │ فرد   │ مرسل  │ 100   │ -             │ -       │ -     │ [...]   │
```

**Action buttons per row** (conditional):

| Button | Icon | Label | When Visible |
|--------|------|-------|-------------|
| PreAuth | `pi pi-bolt` | طلب موافقة | Insurance invoice + no preauth yet |
| Claim | `pi pi-send` | إرسال مطالبة | Has preauth ref + results released |
| View FHIR | `pi pi-code` | عرض FHIR | Any invoice with NPHIES transaction |

**PreAuth Dialog**:

```
┌─── طلب موافقة مسبقة — فاتورة INV-002 ──────────────────────────────┐
│                                                                        │
│  المريض: فاطمة حسن إبراهيم    │    التأمين: بوبا العربية             │
│                                                                        │
│  ┌─ الفحوصات ──────────────────────────────────────────────────────┐   │
│  │  ☑ CBC (تعداد الدم)          │ 65.000 ر.س  │ 920010101          │   │
│  │  ☑ TSH (هرمون الغدة)         │ 85.000 ر.س  │ 920030201          │   │
│  │  ☑ Lipid Panel               │ 70.000 ر.س  │ 920020101          │   │
│  │──────────────────────────────────────────────────────────────────│   │
│  │  المجموع: 220.000 ر.س                                          │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                        │
│  [إلغاء]                                          [طلب الموافقة المسبقة]│
│                                                                        │
│  ── After Response ──                                                  │
│  ┌─ نتيجة الموافقة ────────────────────────────────────────────────┐   │
│  │  ✓ معتمد                                                        │   │
│  │  رقم الموافقة: PA-1712045600000                                 │   │
│  │  صالح حتى: 2026-05-02                                          │   │
│  │  المبلغ المعتمد: 220.000 ر.س                                   │   │
│  │                                                                  │   │
│  │  الفحص          │ المقدّم    │ المعتمد                           │   │
│  │  CBC             │ 65 ر.س   │ 65 ر.س ✓                         │   │
│  │  TSH             │ 85 ر.س   │ 85 ر.س ✓                         │   │
│  │  Lipid Panel     │ 70 ر.س   │ 70 ر.س ✓                         │   │
│  └──────────────────────────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────────────────────┘
```

**Claim Dialog** (similar structure, with adjudication breakdown showing submitted / eligible / benefit / copay per item).

**FHIR Viewer Dialog**:

```
┌─── عرض FHIR Bundle ─────────────────────────────────────────────────┐
│                                                                        │
│  [طلب FHIR]  [استجابة FHIR]    (two tabs)                            │
│                                                                        │
│  ┌──────────────────────────────────────────────────────────────────┐   │
│  │ {                                                                │   │
│  │   "resourceType": "Bundle",                                      │   │
│  │   "id": "550e8400-e29b-41d4-a716-...",                          │   │
│  │   "type": "message",                                             │   │
│  │   "entry": [                                                     │   │
│  │     {                                                            │   │
│  │       "resource": {                                              │   │
│  │         "resourceType": "MessageHeader",                         │   │
│  │         ...                                                      │   │
│  │ (with syntax highlighting: keys=blue, strings=green,             │   │
│  │  numbers=purple, braces=grey)                                    │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                        │
│  [نسخ JSON]                                           [إغلاق]         │
└────────────────────────────────────────────────────────────────────────┘
```

#### 4.3 UX Flow

**PreAuth Flow**:
1. User clicks "طلب موافقة" button on an insurance invoice row.
2. Dialog opens showing invoice details and test list. All tests pre-selected (checkboxes).
3. User can deselect tests (optional -- rare use case).
4. User clicks "طلب الموافقة المسبقة". Button shows spinner.
5. API call: `POST /api/nphies/preauth { invoice_id, investigation_ids }`.
6. Response replaces the test list with the approval result.
7. If approved: Green success section with ref number and per-item breakdown. Dialog stays open for review. "إغلاق" button dismisses.
8. If denied: Red section with rejection reason. Dialog stays open.
9. On close: Table refreshes. The NPHIES Status column updates.

**Claim Flow**:
1. User clicks "إرسال مطالبة" on an invoice that has preauth approval and released results.
2. Dialog opens showing the preauth ref, approved amount, and test list.
3. User clicks "إرسال المطالبة". Button shows spinner.
4. API call: `POST /api/nphies/claim { invoice_id, preauth_ref }`.
5. Response shows adjudication: per-item submitted/covered/copay breakdown.
6. Invoice table updates with new amounts in "Covered" and "Copay" columns.

**FHIR Viewer**:
1. User clicks "عرض FHIR" on any invoice with NPHIES transactions.
2. Dialog shows two tabs: Request JSON and Response JSON.
3. JSON is pretty-printed with syntax highlighting (CSS-based, not a library).
4. "نسخ JSON" copies to clipboard with toast confirmation.

#### 4.4 Visual Design

- **NPHIES Status column badges** (PrimeNG `p-tag`):
  - `pending` (معلّق): `severity="warn"`, icon `pi pi-clock`
  - `authorized` (معتمد): `severity="info"`, icon `pi pi-check`, shows truncated PA ref
  - `claimed` (تم المطالبة): `severity="success"`, icon `pi pi-send`
  - `rejected` (مرفوض): `severity="danger"`, icon `pi pi-times`
  - `paid` (مدفوع): `severity="success"`, icon `pi pi-check-circle`
  - `null` (no NPHIES): Shows dash "-"

- **Covered/Copay columns**: `font-weight: 600`, `text-align: center`. Covered in green, Copay in amber. Format: `132.000` (3 decimal places for SAR).

- **PreAuth dialog**: `width: 700px`, `maximizable: true`. Test list uses `p-table` with checkboxes. Approval result section has left border: `4px solid #10b981` for approved, `4px solid #ef4444` for denied.

- **Claim adjudication table**: 4 columns (Test, Submitted, Covered, Copay). Footer row shows totals in bold. Covered column: green text. Copay column: amber text.

- **FHIR viewer**: `font-family: 'JetBrains Mono', 'Fira Code', monospace`, `font-size: 0.75rem`, `line-height: 1.5`, `background: #1e1e2e` (dark), `color: #cdd6f4`. Scrollable container with `max-height: 500px`. Syntax highlighting via CSS classes: `.json-key { color: #89b4fa }`, `.json-string { color: #a6e3a1 }`, `.json-number { color: #cba6f7 }`, `.json-boolean { color: #f38ba8 }`.

- **Action buttons**: Small icon-only buttons with tooltips. Using PrimeNG `p-button` with `text` and `rounded` styles. Colors: PreAuth = blue, Claim = gold (#ffc107), FHIR = gray.

#### 4.5 Component Architecture

Add to existing `LisInvoicesComponent`:

```typescript
// New signals
nphiesEnabled = computed(() => this.nphiesService.nphiesEnabled());

// PreAuth dialog
preAuthDialogVisible = signal(false);
preAuthInvoice = signal<LisInvoice | null>(null);
preAuthInvestigations = signal<any[]>([]);
preAuthSelectedIds = signal<Set<number>>(new Set());
preAuthLoading = signal(false);
preAuthResult = signal<NphiesPreAuthResponse | null>(null);

// Claim dialog
claimDialogVisible = signal(false);
claimInvoice = signal<LisInvoice | null>(null);
claimLoading = signal(false);
claimResult = signal<NphiesClaimResponse | null>(null);

// FHIR viewer
fhirDialogVisible = signal(false);
fhirRequest = signal<any>(null);
fhirResponse = signal<any>(null);
fhirActiveTab = signal(0);

// Methods
openPreAuth(invoice: LisInvoice): void { /* ... */ }
submitPreAuth(): void { /* ... */ }
openClaim(invoice: LisInvoice): void { /* ... */ }
submitClaim(): void { /* ... */ }
openFhirViewer(invoice: LisInvoice): void { /* ... */ }
copyFhirJson(): void { /* ... */ }

// Helpers
canRequestPreAuth(invoice: LisInvoice): boolean {
  return this.hasInsurance(invoice) && !invoice.nphies_preauth_ref && this.nphiesEnabled();
}
canSubmitClaim(invoice: LisInvoice): boolean {
  return !!invoice.nphies_preauth_ref && !invoice.nphies_claim_status?.includes('claimed')
    && this.nphiesEnabled();
}
hasFhirData(invoice: LisInvoice): boolean {
  return !!invoice.nphies_preauth_ref || !!invoice.nphies_claim_status;
}
```

For JSON syntax highlighting, create a pure pipe:
```typescript
@Pipe({ name: 'jsonHighlight', standalone: true })
export class JsonHighlightPipe implements PipeTransform {
  transform(value: any): string {
    // Returns HTML string with <span class="json-*"> wrappers
  }
}
```

#### 4.6 RTL/Arabic Considerations

- Adjudication amounts are right-aligned in RTL (numbers column)
- FHIR JSON viewer always LTR (`direction: ltr`) since JSON is English
- PreAuth reference numbers LTR
- Dialog buttons: Cancel on inline-start, Submit on inline-end

#### 4.7 Responsive Behavior

- **Desktop**: Table shows all columns including NPHIES Status, Covered, Copay
- **Tablet**: NPHIES columns collapse into a single "NPHIES" column that shows status badge only. Covered/Copay visible in detail dialog.
- **Mobile**: NPHIES data only visible in invoice detail view, not in table

#### 4.8 Accessibility

- Dialog titles use `aria-labelledby`
- Adjudication table is a proper `<table>` with `<th>` headers
- Copy button provides toast feedback (not just clipboard change)
- Color-coded badges also have text labels -- never rely on color alone

#### 4.9 Edge Cases

- **Invoice has no insurance**: PreAuth and Claim buttons hidden. NPHIES columns show dash.
- **PreAuth already submitted**: Button disabled with tooltip "تم طلب الموافقة المسبقة مسبقاً"
- **Claim submitted before results released**: Backend returns 422. Show error: "يجب تحرير النتائج قبل إرسال المطالبة"
- **PreAuth denied, want to retry**: Show "إعادة الطلب" button that clears previous result and re-submits
- **Network timeout during submission**: Show error with retry option. Invoice table shows "pending" status (not "error" -- the backend may still process).
- **FHIR JSON is empty** (no transactions yet): FHIR viewer button hidden
- **Invoice has preauth but no claim yet**: Show "Claim" button. FHIR viewer shows preauth FHIR only.
- **Very long FHIR bundle** (>500 lines): Virtual scrolling not needed -- JSON viewer is scrollable with fixed height

---

### Phase 5: Screen 5 -- Transaction Log + Dashboard Widget

**Files**: `src/app/features/lis/nphies-log/nphies-log.component.ts` + `dashboard/lis-dashboard.component.ts`
**Task**: #1472

#### 5.1 User Story

**Log page -- Who**: Lab administrator or billing manager.
**When**: To audit NPHIES transactions, debug failed requests, track daily activity.
**Why**: Regulatory compliance, troubleshooting, financial reconciliation.

**Dashboard widget -- Who**: Any lab user.
**When**: On the main dashboard, at a glance.
**Why**: Quick view of today's NPHIES activity volume and covered amounts.

#### 5.2 UI Layout -- Transaction Log Page

```
┌─────────────────────────────────────────────────────────────────────────┐
│ PageHeader: "سجل معاملات NPHIES"  [تحديث] [تصدير Excel]               │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│ ┌─ Summary Cards ──────────────────────────────────────────────────────┐│
│ │ [تحقق تأمين: 15] [موافقة مسبقة: 12] [مطالبة: 8] [خطأ: 2]         ││
│ └──────────────────────────────────────────────────────────────────────┘│
│                                                                         │
│ ┌─ Filters ────────────────────────────────────────────────────────────┐│
│ │ [النوع ▾] [الحالة ▾] [من تاريخ] [إلى تاريخ] [بحث مريض...] [مسح]  ││
│ └──────────────────────────────────────────────────────────────────────┘│
│                                                                         │
│ ┌─ Table ──────────────────────────────────────────────────────────────┐│
│ │ التاريخ      │ النوع        │ المريض     │ الطلب   │ الحالة │ [👁]  ││
│ │──────────────┼──────────────┼────────────┼─────────┼────────┼───────││
│ │ 14:23 04/02  │ 🟢 تحقق تأمين│ أحمد خالد  │ REQ-045 │ ✓ ناجح │ [👁]  ││
│ │ 14:20 04/02  │ 🔵 موافقة    │ فاطمة حسن  │ REQ-044 │ ✓ ناجح │ [👁]  ││
│ │ 14:15 04/02  │ 🟡 مطالبة    │ خالد عبدالله│ REQ-043 │ ✗ خطأ  │ [👁]  ││
│ │ ...          │              │            │         │        │       ││
│ └──────────────────────────────────────────────────────────────────────┘│
│                                                                         │
│ [< 1 2 3 ... >]                                                        │
│                                                                         │
│ ── JSON Viewer (expandable below table, or side panel) ──              │
│ ┌──────────────────────────────────────────────────────────────────────┐│
│ │ [طلب FHIR] [استجابة FHIR]                                          ││
│ │                                                                      ││
│ │ { "resourceType": "Bundle", ... }                                    ││
│ │                                                                      ││
│ │ [نسخ JSON] [طي]                                                      ││
│ └──────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────┘
```

#### 5.3 UI Layout -- Dashboard Widget

Inserted as a conditional card in the existing stat-cards grid:

```
@if (nphiesEnabled()) {
  <div class="stat-card stat-indigo clickable" (click)="drillDown('nphies-log')">
    <div class="stat-icon-wrap"><i class="pi pi-shield"></i></div>
    <div class="stat-info">
      <span class="stat-value">{{ nphiesTodayCount() }}</span>
      <span class="stat-label">{{ 'LIS.NPHIES.DASHBOARD.TITLE' | translate }}</span>
    </div>
    <div class="stat-decoration"></div>
    <i class="stat-arrow pi pi-arrow-right"></i>
  </div>
}
```

When the stat-card is hovered, show a tooltip breakdown:
```
NPHIES اليوم
تحقق تأمين: 15
موافقة مسبقة: 12
مطالبة مرسلة: 8
مبلغ مغطّى: 4,250 ر.س
```

#### 5.4 UX Flow -- Transaction Log

1. Page loads with summary cards showing today's counts (loading skeleton first).
2. Table loads page 1 of transactions (25 per page, server-side pagination).
3. **Filters**: Type dropdown (eligibility, preauth, claim, cancel, poll), Status dropdown (sent, success, error, timeout), date range picker, text search (patient name or request number). All filters trigger a re-fetch via API query params.
4. **Row click**: Expands a detail panel below the row (accordion-style) showing the FHIR JSON viewer with two tabs (request/response).
5. **Summary cards update**: When filters change, summary cards show filtered counts (from API response metadata or computed client-side from current page -- prefer API-side if available).
6. **Export**: Calls the transactions API with `format=excel` parameter. Downloads file.
7. **Auto-refresh**: The page refreshes every 60 seconds (using `setInterval`). A subtle "آخر تحديث: 14:25" timestamp shows in the header. Manual refresh button also available.

#### 5.5 UX Flow -- Dashboard Widget

1. On dashboard load, if `nphiesEnabled()`, make an additional API call: `GET /api/nphies/transactions?date=today&per_page=1` (just to get summary counts from the response -- or a dedicated summary endpoint if available).
2. Display total transaction count as the stat-card value.
3. Click navigates to `/lab/nphies-log`.
4. Tooltip (via PrimeNG `pTooltip`) shows breakdown of counts by type and covered amount.

#### 5.6 Visual Design -- Transaction Log

- **Summary cards**: 4 small cards in a horizontal row. Each card: `width: fit-content`, `min-width: 140px`, `padding: 12px 20px`, `border-radius: 12px`, `display: flex`, `align-items: center`, `gap: 8px`.
  - Eligibility: `background: #ecfdf5`, icon `pi pi-check-circle` in green
  - PreAuth: `background: #eff6ff`, icon `pi pi-bolt` in blue
  - Claim: `background: #fefce8`, icon `pi pi-send` in amber
  - Error: `background: #fef2f2`, icon `pi pi-times-circle` in red

- **Filter bar**: Same pattern as other LIS pages. Horizontal flex row with gap. Dropdowns use PrimeNG `Select`. Date picker uses PrimeNG `DatePicker` with range mode. Search input with `pi pi-search` icon.

- **Table**: PrimeNG `p-table` with `paginator`, `lazy`, `rows=25`. Type column shows colored dot + text. Status column shows badge (same palette as invoice NPHIES status).

- **Type colors**: `eligibility` = green dot, `preauth` = blue dot, `claim` = amber dot, `cancel` = red dot, `poll` = gray dot

- **Expandable row**: Uses PrimeNG row expansion template. FHIR viewer same style as invoice dialog viewer (dark background, monospace, syntax highlighted).

- **Timestamp format**: Arabic locale `ar-SA`, relative time when <24h ("قبل ساعتين"), full date otherwise

#### 5.7 Visual Design -- Dashboard Widget

- Uses existing `.stat-indigo` color scheme (gradient: `#818cf8` to `#6366f1`)
- Icon: `pi pi-shield`
- Matches all other stat-cards exactly

#### 5.8 Component Architecture -- Transaction Log

```typescript
@Component({
  selector: 'app-nphies-log',
  standalone: true,
  imports: [ /* ... */ ],
  providers: [MessageService],
})
export class NphiesLogComponent implements OnInit, OnDestroy {
  private nphiesService = inject(NphiesService);
  lang = inject(LanguageService);

  // State
  loading = signal(true);
  tableData = signal<NphiesTransaction[]>([]);
  totalRecords = signal(0);
  first = 0;
  rows = 25;

  // Filters
  typeFilter = signal<string | null>(null);
  statusFilter = signal<string | null>(null);
  dateFrom = signal<Date | null>(null);
  dateTo = signal<Date | null>(null);
  searchQuery = signal('');

  // Summary
  summaryLoading = signal(true);
  todayCounts = signal<{ eligibility: number; preauth: number; claim: number; error: number }>({
    eligibility: 0, preauth: 0, claim: 0, error: 0,
  });

  // Detail viewer
  expandedRowId = signal<number | null>(null);
  selectedTransaction = signal<NphiesTransaction | null>(null);
  fhirTab = signal(0);

  // Auto-refresh
  private refreshInterval: any;
  lastRefresh = signal<Date>(new Date());

  // Filter options (same pattern as other LIS pages)
  typeOptions = [ /* ... */ ];
  statusOptions = [ /* ... */ ];
}
```

#### 5.9 Component Architecture -- Dashboard Addition

Add to existing `LisDashboardComponent`:

```typescript
nphiesEnabled = computed(() => this.nphiesService.nphiesEnabled());
nphiesTodayCount = signal(0);
nphiesTodayBreakdown = signal<{ eligibility: number; preauth: number; claim: number; covered: number }>({
  eligibility: 0, preauth: 0, claim: 0, covered: 0,
});
```

In the existing `forkJoin` data loading block, add a conditional call:
```typescript
if (this.nphiesService.nphiesEnabled()) {
  this.nphiesService.getTransactions(1, 1, { date: today }).subscribe({
    next: (res) => {
      // Extract counts from meta or data
      this.nphiesTodayCount.set(res.meta?.total || 0);
    },
    error: () => {},
  });
}
```

Add `drillDown('nphies-log')` case to navigate to `/lab/nphies-log`.

#### 5.10 RTL/Arabic Considerations

- Summary card numbers are large and center-aligned
- Date/time in Arabic locale (`ar-SA`)
- Filter bar flows RTL naturally
- FHIR JSON viewer always LTR
- Pagination controls respect RTL (next/prev arrows flip)

#### 5.11 Responsive Behavior

- **Desktop**: Summary cards in horizontal row, table full-width
- **Tablet**: Summary cards wrap to 2x2 grid, table scrollable
- **Mobile**: Summary cards stack vertically, table shows essential columns only (date, type, status), detail in expandable row

#### 5.12 Accessibility

- Summary cards have `aria-label` describing the count
- Table rows are focusable, Enter expands detail
- FHIR viewer has `aria-label="FHIR JSON Bundle viewer"`
- Auto-refresh can be paused (Escape key or pause button)

#### 5.13 Edge Cases

- **No transactions yet**: Empty state illustration: "لا توجد معاملات NPHIES بعد" with a muted shield icon
- **Transactions API returns error**: Show error state with retry, same pattern as dashboard
- **Very large JSON** (>100KB): Lazy-render the JSON viewer -- only parse/highlight when expanded
- **Date filter spans multiple days**: Summary cards show totals for filtered range (not just today)
- **Transaction in "sent" status** (no response yet): Show amber "مرسل" badge, response tab shows "في انتظار الاستجابة..."

---

## Testing Strategy

Since tests are disabled in this project (`skipTests: true` in angular.json), testing is manual:

- **Manual testing checklist per screen**:
  1. NPHIES disabled: verify zero NPHIES UI renders
  2. NPHIES enabled: verify all UI appears
  3. Arabic layout: verify RTL alignment, Arabic text
  4. English layout: verify LTR switch works
  5. API success: verify happy path
  6. API error (simulate via browser DevTools network throttling or offline)
  7. API timeout (simulate via DevTools slow 3G)
  8. Mobile responsive: Chrome DevTools mobile view

- **Integration testing with NPHIES Simulator**:
  - Simulator at `https://moonui.elbaset.com/nphies/` (or local `http://localhost:3456`)
  - Test patients: `1234567890` (Ahmad, Tawuniya Gold), `2098765432` (Fatima, Bupa Silver)
  - Test full flow: eligibility -> preauth -> claim -> transaction log

## Risks & Mitigations

| Risk | Severity | Mitigation |
|------|----------|------------|
| **Feature flag loading race condition**: Components render before config API returns | High | The `NphiesService.nphiesEnabled()` signal defaults to `false`. The layout component loads the flag in `ngOnInit()`. All `@if (nphiesEnabled())` blocks simply do not render until the signal turns `true`. No flash of content. |
| **Request wizard becomes too complex**: Already 950+ lines, adding eligibility logic makes it heavier | Medium | Extract the eligibility banner into a standalone child component `NphiesEligibilityBannerComponent` that takes `patient` and `insurerCode` as inputs and manages its own state. Parent only listens to an `eligibilityResult` output. |
| **Invoice page action buttons clutter**: Adding 3 more buttons to each row | Medium | Use a single "NPHIES" split-button (PrimeNG `SplitButton`) that groups all NPHIES actions in one dropdown. Or use icon-only buttons with tooltips (smaller footprint). |
| **FHIR JSON rendering performance**: Large bundles could freeze the UI | Low | Use `JSON.stringify(obj, null, 2)` and render as `<pre>` with CSS-based highlighting. Avoid DOM-heavy libraries. Set `max-height: 500px` with overflow scroll. |
| **Backend API not returning expected fields**: New NPHIES fields on invoice model may not be populated yet | Medium | Use optional chaining and nullish coalescing everywhere. Hide NPHIES columns/buttons when data is null. |
| **Concurrent eligibility checks**: User rapidly changes patient/insurance | Medium | Use `switchMap` on the debounced trigger to auto-cancel previous in-flight requests. |

## Success Criteria

- [ ] `nphies_enabled=false`: Zero NPHIES UI visible anywhere in the LIS
- [ ] `nphies_enabled=true`: All 5 screens render correctly in Arabic RTL
- [ ] Settings page: can save config, upload certificates, test connection, map SBSCS codes
- [ ] Patient form: Saudi ID fields appear, validation works per ID type, data saves correctly
- [ ] Request wizard: eligibility check auto-triggers for insured patients, shows result banner, override works
- [ ] Invoices: PreAuth dialog submits and shows approval, Claim dialog submits and shows adjudication, FHIR viewer displays JSON
- [ ] Dashboard: NPHIES KPI card appears with today's count, navigates to log
- [ ] Transaction log: paginated table with filters, expandable FHIR viewer, summary cards
- [ ] All text is translatable (Arabic + English)
- [ ] Responsive on tablet (768px) and mobile (375px)
- [ ] No console errors, no TypeScript compilation errors
- [ ] Builds successfully with `npx ng build --base-href /app/`

## Implementation Order (Recommended)

| Order | Phase | Task | Estimated Time | Dependencies |
|-------|-------|------|---------------|--------------|
| 1 | Phase 0 | Models + Service + Feature Flag + Routes + Translations | 2-3 hours | None |
| 2 | Phase 1 | NPHIES Settings Page | 3-4 hours | Phase 0 |
| 3 | Phase 2 | Patient Saudi ID Fields | 2-3 hours | Phase 0 |
| 4 | Phase 3 | Eligibility in Request Wizard | 3-4 hours | Phase 0, Phase 2 |
| 5 | Phase 4 | PreAuth + Claim on Invoices | 4-5 hours | Phase 0 |
| 6 | Phase 5 | Transaction Log + Dashboard Widget | 3-4 hours | Phase 0 |

**Total estimated**: 17-23 hours of implementation.

Each phase is independently deliverable and testable. Phase 0 is the only hard dependency -- all other phases can proceed in parallel after Phase 0 is complete. Phases 2 and 3 have a soft dependency (eligibility works better when patients have Saudi IDs), but can be built independently.

---

## Key File Paths Summary

**New files to create:**
- `/home/moonui/public_html/moon-erp/src/app/core/models/nphies.model.ts`
- `/home/moonui/public_html/moon-erp/src/app/core/services/nphies.service.ts`
- `/home/moonui/public_html/moon-erp/src/app/features/lis/nphies-settings/nphies-settings.component.ts`
- `/home/moonui/public_html/moon-erp/src/app/features/lis/nphies-settings/nphies-settings.component.html`
- `/home/moonui/public_html/moon-erp/src/app/features/lis/nphies-settings/nphies-settings.component.scss`
- `/home/moonui/public_html/moon-erp/src/app/features/lis/nphies-log/nphies-log.component.ts`
- `/home/moonui/public_html/moon-erp/src/app/features/lis/nphies-log/nphies-log.component.html`
- `/home/moonui/public_html/moon-erp/src/app/features/lis/nphies-log/nphies-log.component.scss`

**Existing files to modify:**
- `/home/moonui/public_html/moon-erp/src/app/core/models/lis-patient.model.ts` -- add `national_id_type`, `passport_country`
- `/home/moonui/public_html/moon-erp/src/app/core/models/lis-invoice.model.ts` -- add 4 NPHIES fields
- `/home/moonui/public_html/moon-erp/src/app/features/lis/lis-standalone.routes.ts` -- add 2 routes
- `/home/moonui/public_html/moon-erp/src/app/features/lis/lis-layout/lis-layout.component.ts` -- feature flag init, conditional nav items, breadcrumb map
- `/home/moonui/public_html/moon-erp/src/app/features/lis/patients/lis-patients.component.ts` -- Saudi ID fields, custom validator
- `/home/moonui/public_html/moon-erp/src/app/features/lis/patients/lis-patients.component.html` -- NPHIES form section
- `/home/moonui/public_html/moon-erp/src/app/features/lis/request-wizard-v2/request-wizard-v2.component.ts` -- eligibility check logic
- `/home/moonui/public_html/moon-erp/src/app/features/lis/request-wizard-v2/request-wizard-v2.component.html` -- eligibility banner
- `/home/moonui/public_html/moon-erp/src/app/features/lis/invoices/lis-invoices.component.ts` -- preauth/claim dialogs, FHIR viewer
- `/home/moonui/public_html/moon-erp/src/app/features/lis/invoices/lis-invoices.component.html` -- NPHIES columns, dialogs
- `/home/moonui/public_html/moon-erp/src/app/features/lis/dashboard/lis-dashboard.component.ts` -- NPHIES KPI card
- `/home/moonui/public_html/moon-erp/src/app/features/lis/dashboard/lis-dashboard.component.html` -- NPHIES stat-card
- `/home/moonui/public_html/moon-erp/src/assets/i18n/ar.json` -- ~100 new translation keys
- `/home/moonui/public_html/moon-erp/src/assets/i18n/en.json` -- ~100 new translation keys

**Reference files (read-only context):**
- `/home/moonui/public_html/moon-erp/docs/NPHIES-INTEGRATION.md` -- backend API spec and flow diagrams
- `/home/moonui/nphies-simulator/server.js` -- simulator response structures for testing
- `/home/moonui/public_html/moon-erp/src/app/features/lis/CLAUDE.md` -- LIS module architecture reference