> ## 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 Module Settings Specification

> Version: 1.0.0 Status: 📝 Planned Related Specs: FA-01, FA-02, FA-04, FA-08 Constitution Reference: Section 3.1 (Module Settings Pattern)

**Version:** 1.0.0\
**Status:** 📝 Planned\
**Related Specs:** FA-01, FA-02, FA-04, FA-08\
**Constitution Reference:** Section 3.1 (Module Settings Pattern)

***

## 1. Overview

Every domain core should have a `{core}_module_settings` table and admin page for organization-level configuration. This document specifies the FA (Finance & Accounting) module settings implementation.

***

## 2. Database Schema

### Table: `fa_module_settings`

```sql theme={null}
CREATE TABLE fa_module_settings (
  -- Standard columns (required for all module settings)
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  organization_id UUID NOT NULL UNIQUE REFERENCES pf_organizations(id),
  created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
  updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
  created_by UUID REFERENCES pf_profiles(id),
  updated_by UUID REFERENCES pf_profiles(id),
  
  -- FA-specific configuration
  -- General Ledger Settings
  default_cash_account_id UUID REFERENCES fa_accounts(id),
  default_ar_account_id UUID REFERENCES fa_accounts(id),
  default_ap_account_id UUID REFERENCES fa_accounts(id),
  default_retained_earnings_account_id UUID REFERENCES fa_accounts(id),
  retained_earnings_close_day INTEGER DEFAULT 31 CHECK (retained_earnings_close_day BETWEEN 1 AND 31),
  
  -- Fiscal Year Settings
  fiscal_year_start_month INTEGER DEFAULT 1 CHECK (fiscal_year_start_month BETWEEN 1 AND 12),
  fiscal_year_start_day INTEGER DEFAULT 1 CHECK (fiscal_year_start_day BETWEEN 1 AND 31),
  auto_create_periods BOOLEAN DEFAULT true,
  period_close_requires_approval BOOLEAN DEFAULT true,
  
  -- Accounts Payable Settings
  default_payment_terms_days INTEGER DEFAULT 30,
  require_po_for_bills BOOLEAN DEFAULT false,
  bill_approval_threshold NUMERIC(12,2), -- NULL = all bills require approval
  three_way_match_required BOOLEAN DEFAULT false,
  
  -- Accounts Receivable Settings
  default_invoice_terms_days INTEGER DEFAULT 30,
  late_fee_percent NUMERIC(5,2) DEFAULT 0.00,
  late_fee_grace_days INTEGER DEFAULT 0,
  auto_apply_payments BOOLEAN DEFAULT true,
  
  -- Bank Reconciliation Settings
  require_bank_rec_approval BOOLEAN DEFAULT true,
  bank_rec_variance_threshold NUMERIC(12,2) DEFAULT 0.01,
  
  -- Budget Settings
  budget_approval_required BOOLEAN DEFAULT true,
  budget_warning_threshold NUMERIC(5,2) DEFAULT 90.00, -- Warn at 90% of budget
  budget_critical_threshold NUMERIC(5,2) DEFAULT 100.00, -- Alert at 100% of budget
  allow_over_budget_posting BOOLEAN DEFAULT true,
  
  -- Reporting Settings
  default_report_format report_format DEFAULT 'pdf',
  consolidation_enabled BOOLEAN DEFAULT false,
  fund_accounting_enabled BOOLEAN DEFAULT true,
  
  -- Audit & Compliance
  require_dual_approval_over NUMERIC(12,2), -- NULL = no dual approval required
  restrict_prior_period_posting BOOLEAN DEFAULT true,
  days_back_posting_allowed INTEGER DEFAULT 5,
  
  -- Notifications
  notify_period_close BOOLEAN DEFAULT true,
  notify_budget_alerts BOOLEAN DEFAULT true,
  notify_large_transactions BOOLEAN DEFAULT true,
  large_transaction_threshold NUMERIC(12,2) DEFAULT 10000.00
);

-- RLS Policies
ALTER TABLE fa_module_settings ENABLE ROW LEVEL SECURITY;

CREATE POLICY "fa_module_settings_view"
  ON fa_module_settings FOR SELECT
  USING (has_org_access(auth.uid(), organization_id));

CREATE POLICY "fa_module_settings_org_admin_manage"
  ON fa_module_settings FOR ALL
  USING (
    EXISTS (
      SELECT 1 FROM pf_user_roles
      WHERE user_id = auth.uid()
        AND organization_id = fa_module_settings.organization_id
        AND role IN ('org_admin', 'platform_admin')
    )
  );

-- Trigger for updated_at
CREATE TRIGGER set_fa_module_settings_updated_at
  BEFORE UPDATE ON fa_module_settings
  FOR EACH ROW
  EXECUTE FUNCTION handle_updated_at();

-- Seed function
CREATE FUNCTION seed_fa_module_settings(_org_id UUID, _created_by UUID)
RETURNS UUID AS $$
DECLARE
  _settings_id UUID;
BEGIN
  INSERT INTO fa_module_settings (organization_id, created_by)
  VALUES (_org_id, _created_by)
  RETURNING id INTO _settings_id;
  
  RETURN _settings_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
```

***

## 3. Frontend Implementation

### 3.1 Admin Page

**Location:** `src/cores/fa/pages/FASettings.tsx`\
**Route:** `/fa/settings`\
**Access:** org\_admin only

**Layout:**

```tsx theme={null}
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/shared/ui/tabs';
import { FASettingsForm } from '../components/FASettingsForm';

export default function FASettings() {
  const { settings, isLoading } = useFAModuleSettings();
  
  return (
    <div className="container mx-auto py-6 space-y-6">
      <div>
        <h1 className="text-3xl font-bold">Finance Settings</h1>
        <p className="text-muted-foreground mt-1">
          Configure organization-wide finance and accounting defaults
        </p>
      </div>
      
      {isLoading ? (
        <div>Loading settings...</div>
      ) : (
        <FASettingsForm settings={settings} />
      )}
    </div>
  );
}
```

### 3.2 Settings Form Component

**Location:** `src/cores/fa/components/FASettingsForm.tsx`

**Tabs Structure:**

1. **General Ledger** - Default accounts, fiscal year settings
2. **Accounts Payable** - Payment terms, approval thresholds, PO requirements
3. **Accounts Receivable** - Invoice terms, late fees, payment application
4. **Bank Reconciliation** - Approval requirements, variance thresholds
5. **Budgeting** - Approval workflow, alert thresholds, over-budget controls
6. **Reporting** - Default formats, consolidation, fund accounting
7. **Compliance** - Dual approval, period restrictions, audit controls
8. **Notifications** - Alert preferences for period close, budgets, large transactions

**Example Tab:**

```tsx theme={null}
<TabsContent value="general-ledger">
  <Card>
    <CardHeader>
      <CardTitle>General Ledger Settings</CardTitle>
      <CardDescription>
        Default accounts and fiscal year configuration
      </CardDescription>
    </CardHeader>
    <CardContent className="space-y-4">
      <FormField
        control={form.control}
        name="default_cash_account_id"
        render={({ field }) => (
          <FormItem>
            <FormLabel>Default Cash Account</FormLabel>
            <AccountSelector
              {...field}
              accountTypes={['asset']}
              placeholder="Select cash account"
            />
            <FormDescription>
              Used for cash receipts and payments when no account specified
            </FormDescription>
          </FormItem>
        )}
      />
      
      {/* More fields... */}
    </CardContent>
  </Card>
</TabsContent>
```

### 3.3 Hook

**Location:** `src/cores/fa/hooks/useFAModuleSettings.ts`

```typescript theme={null}
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { toast } from '@/shared/lib/hooks/use-toast';

export function useFAModuleSettings() {
  const queryClient = useQueryClient();
  
  const { data: settings, isLoading, error } = useQuery({
    queryKey: ['fa-module-settings'],
    queryFn: async () => {
      const { data: { user } } = await supabase.auth.getUser();
      if (!user) throw new Error('Not authenticated');
      
      // Get user's org
      const { data: roles } = await supabase
        .from('pf_user_roles')
        .select('organization_id')
        .eq('user_id', user.id)
        .limit(1)
        .single();
      
      if (!roles) throw new Error('No organization found');
      
      // Get or create settings
      let { data: settings, error } = await supabase
        .from('fa_module_settings')
        .select('*')
        .eq('organization_id', roles.organization_id)
        .single();
      
      if (error && error.code === 'PGRST116') {
        // Settings don't exist, create defaults
        const { data: newSettings, error: createError } = await supabase
          .rpc('seed_fa_module_settings', {
            _org_id: roles.organization_id,
            _created_by: user.id
          });
        
        if (createError) throw createError;
        
        // Fetch the created settings
        const { data: fetchedSettings } = await supabase
          .from('fa_module_settings')
          .select('*')
          .eq('organization_id', roles.organization_id)
          .single();
        
        return fetchedSettings;
      }
      
      if (error) throw error;
      return settings;
    }
  });
  
  const updateMutation = useMutation({
    mutationFn: async (updates: Partial<FAModuleSettings>) => {
      const { data, error } = await supabase
        .from('fa_module_settings')
        .update(updates)
        .eq('id', settings?.id)
        .select()
        .single();
      
      if (error) throw error;
      return data;
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['fa-module-settings'] });
      toast({
        title: 'Settings saved',
        description: 'Finance settings have been updated successfully.'
      });
    },
    onError: (error: Error) => {
      toast({
        title: 'Save failed',
        description: error.message,
        variant: 'destructive'
      });
    }
  });
  
  return {
    settings,
    isLoading,
    error,
    update: updateMutation.mutate,
    isUpdating: updateMutation.isPending
  };
}
```

***

## 4. Navigation Integration

### Add to FA Module Registry

**File:** `src/platform/navigation/module-registry.ts`

```typescript theme={null}
// In FA module config
{
  id: 'fa',
  name: 'Finance',
  icon: DollarSign,
  routes: [
    // ... existing routes
    {
      path: '/fa/settings',
      label: 'Settings',
      icon: Settings,
      permission: 'fa.settings.admin' // V2 permission-based access control
    }
  ]
}
```

### Add Route

**File:** `src/App.tsx`

```typescript theme={null}
<Route path="/fa/settings" element={<FASettings />} />
```

***

## 5. Usage in Other FA Modules

### Example: FA-02 (General Ledger)

```typescript theme={null}
// When creating journal entry, use default accounts
const { settings } = useFAModuleSettings();

// Check if posting to prior period is allowed
if (settings?.restrict_prior_period_posting) {
  const daysBack = differenceInDays(new Date(), entryDate);
  if (daysBack > (settings.days_back_posting_allowed || 0)) {
    throw new Error('Cannot post to periods older than configured limit');
  }
}

// Use default cash account if none specified
const cashAccountId = lineItem.account_id || settings?.default_cash_account_id;
```

### Example: FA-04 (Accounts Payable)

```typescript theme={null}
const { settings } = useFAModuleSettings();

// Check if bill requires approval
const requiresApproval = settings?.bill_approval_threshold 
  ? bill.amount >= settings.bill_approval_threshold
  : true; // If threshold is null, all bills require approval

// Check if PO is required
if (settings?.require_po_for_bills && !bill.purchase_order_id) {
  throw new Error('Purchase order is required for all bills');
}
```

### Example: FA-08 (Budgeting)

```typescript theme={null}
const { settings } = useFAModuleSettings();

// Check budget alert thresholds
const warningThreshold = settings?.budget_warning_threshold || 90;
const criticalThreshold = settings?.budget_critical_threshold || 100;

if (variance >= criticalThreshold) {
  // Send critical alert
  await sendBudgetAlert('critical', ...);
} else if (variance >= warningThreshold) {
  // Send warning alert
  await sendBudgetAlert('warning', ...);
}

// Check if over-budget posting is allowed
if (!settings?.allow_over_budget_posting && wouldExceedBudget) {
  throw new Error('Posting would exceed budget and over-budget posting is disabled');
}
```

***

## 6. Implementation Checklist

* [ ] Create `fa_module_settings` table with RLS policies
* [ ] Add `set_updated_at` trigger
* [ ] Create `seed_fa_module_settings()` function
* [ ] Create hook: `useFAModuleSettings.ts`
* [ ] Create form: `FASettingsForm.tsx` with 8 tabs
* [ ] Create page: `FASettings.tsx`
* [ ] Add route to `App.tsx`
* [ ] Add navigation item (org\_admin only)
* [ ] Document in `IMPLEMENTATION_LOG.md`
* [ ] Update FA-01, FA-02, FA-04, FA-08 specs to reference module settings

***

## 7. Testing Requirements

### Unit Tests

* Settings hook fetches and updates correctly
* Default values applied when creating new settings
* Form validation for numeric thresholds

### Integration Tests

* Only org\_admin can access settings page
* Settings auto-created on first access
* Settings apply correctly across FA modules (GL, AP, AR, Budget)

### RLS Tests

* Users in org can view settings
* Only org\_admin can update settings
* Users outside org cannot view/modify settings

***

## 8. Documentation

**Update `IMPLEMENTATION_LOG.md`:**

```markdown theme={null}
### FA-10: Module Settings (✅ Complete)

**Completed:** YYYY-MM-DD

**Database Changes:**
- Created table: `fa_module_settings`
- RLS policies: org view, org_admin manage
- Function: `seed_fa_module_settings()`

**Components & Files:**
- `/src/cores/fa/pages/FASettings.tsx`
- `/src/cores/fa/components/FASettingsForm.tsx`
- `/src/cores/fa/hooks/useFAModuleSettings.ts`

**Testing:**
- RLS tests: ✅ Complete
- Integration tests: ✅ Complete

**Known Limitations:**
- None
```
