# Configuration POS par Company

## Principe

Chaque Company dispose de sa propre instance `PosSettings` qui contrôle entièrement le comportement de sa boutique. Aucun paramètre n'est partagé entre companies. Le manager de chaque company est autonome dans la configuration de sa boutique.

---

## Catégories de Configuration

### 1. Paramètres Généraux

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `is_pos_enabled` | boolean | false | Active/désactive le module POS |
| `shop_name` | string | company.name | Nom commercial de la boutique |
| `currency` | string | 'XOF' | Code devise ISO 4217 |
| `currency_symbol` | string | 'FCFA' | Symbole affiché |
| `tax_rate` | decimal | 18.00 | Taux de TVA par défaut (%) |
| `tax_included` | boolean | true | Prix TTC par défaut |
| `decimal_places` | integer | 0 | Décimales (0 pour XOF) |

**Devises supportées :**
| Code | Symbole | Décimales | Pays |
|------|---------|-----------|------|
| XOF | FCFA | 0 | Sénégal, UEMOA |
| XAF | FCFA | 0 | Cameroun, CEMAC |
| EUR | € | 2 | Europe |
| USD | $ | 2 | International |
| GBP | £ | 2 | Royaume-Uni |
| MAD | DH | 2 | Maroc |

### 2. Paramètres de Stock

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `low_stock_threshold` | integer | 10 | Seuil alerte stock bas |
| `critical_stock_threshold` | integer | 3 | Seuil alerte critique |
| `enable_negative_stock` | boolean | false | Autoriser stock négatif |
| `auto_reorder` | boolean | false | Commande automatique |
| `reorder_point` | integer | 5 | Point de réapprovisionnement |

**Comportement stock négatif :**
- Si `enable_negative_stock = false` : la vente est bloquée quand stock = 0
- Si `enable_negative_stock = true` : la vente passe, stock peut devenir négatif (utile pour les services ou pré-commandes)

### 3. Paramètres de Vente

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `allow_discount` | boolean | true | Remises autorisées |
| `max_discount_percent` | decimal | 50.00 | Remise max globale (%) |
| `require_customer` | boolean | false | Client obligatoire sur chaque vente |
| `allow_credit_sales` | boolean | false | Vente à crédit |
| `credit_limit` | decimal | 0 | Plafond de crédit par client (0 = illimité si crédit autorisé) |
| `enable_loyalty` | boolean | false | Programme fidélité |
| `loyalty_points_per_unit` | integer | 1 | Points par unité monétaire dépensée |

**Interactions :**
- Si `require_customer = true` : le caissier doit sélectionner un client avant de finaliser
- Si `allow_credit_sales = true` et `credit_limit > 0` : la vente à crédit est refusée si le solde dépasse le plafond
- Si `enable_loyalty = true` : les points sont calculés automatiquement et le paiement par fidélité est disponible

### 4. Paramètres de Paiement

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `accepted_payment_methods` | json | ['cash'] | Moyens de paiement |
| `mobile_money_providers` | json | [] | Fournisseurs mobile money |

**Moyens de paiement disponibles :**
| Code | Label | Nécessite référence |
|------|-------|-------------------|
| `cash` | Espèces | Non |
| `mobile_money` | Mobile Money | Oui (n° transaction) |
| `card` | Carte bancaire | Oui (n° autorisation) |
| `transfer` | Virement bancaire | Oui (n° virement) |
| `credit` | Crédit client | Non (auto) |
| `loyalty` | Points fidélité | Non (auto) |
| `check` | Chèque | Oui (n° chèque) |

**Fournisseurs Mobile Money :**
| Code | Label | Pays |
|------|-------|------|
| `orange_money` | Orange Money | Sénégal, Mali, CI |
| `wave` | Wave | Sénégal, CI |
| `free_money` | Free Money | Sénégal |
| `mtn_momo` | MTN Mobile Money | Cameroun, CI |
| `moov_money` | Moov Money | CI, Bénin |
| `airtel_money` | Airtel Money | Multi-pays |

### 5. Paramètres Employés

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `require_employee_pin` | boolean | true | PIN caissier obligatoire |
| `auto_logout_minutes` | integer | 30 | Déconnexion auto caisse (min) |
| `allow_employee_void` | boolean | false | Employé peut annuler une vente |
| `allow_employee_refund` | boolean | false | Employé peut rembourser |

**Impact sur l'interface caissier :**
- `require_employee_pin = true` : modal PIN à l'ouverture de caisse + pour actions sensibles
- `auto_logout_minutes = 0` : pas de déconnexion automatique
- `allow_employee_void = false` : bouton annulation masqué, seul le manager peut annuler
- `allow_employee_refund = false` : l'employé peut demander un remboursement, le manager doit approuver

### 6. Paramètres de Notification

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `low_stock_alert_enabled` | boolean | true | Email alerte stock bas |
| `daily_report_enabled` | boolean | true | Rapport quotidien par email |
| `large_sale_alert_enabled` | boolean | false | Alerte grosse vente |
| `large_sale_threshold` | decimal | 500000 | Seuil alerte vente (FCFA) |

**Destinataires :**
- Alertes envoyées au(x) ROLE_MANAGER de la company
- Via le système `EmailService` existant
- Via `NotificationApp` pour notifications in-app

### 7. Paramètres Ticket de Caisse

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `receipt_header` | text | null | En-tête personnalisé |
| `receipt_footer` | text | null | Pied de page personnalisé |
| `receipt_show_tax` | boolean | true | Afficher détail TVA |
| `receipt_show_logo` | boolean | true | Logo company sur ticket |

**En-tête par défaut (si null) :**
```
{company.name}
{company.address}
{company.city}, {company.country}
Tel: {company.phone}
NINEA: {company.ninea}
```

### 8. Paramètres Code-barres

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `barcode_format` | string | 'EAN13' | Format par défaut |
| `auto_generate_barcode` | boolean | true | Génération auto à la création produit |
| `auto_generate_sku` | boolean | true | Génération auto SKU |

**Formats supportés :**
| Format | Longueur | Usage |
|--------|----------|-------|
| EAN13 | 13 chiffres | Standard international |
| EAN8 | 8 chiffres | Petits produits |
| UPC | 12 chiffres | Standard USA |
| CODE128 | Variable | Usage général |
| QR | Variable | Mobile friendly |

### 9. Paramètres Média (voir POS_MEDIA_ISOLATION.md)

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `storage_quota_mb` | integer | null | Quota stockage (null = illimité) |
| `max_product_images` | integer | 5 | Max images par produit |
| `max_image_size_mb` | integer | 5 | Taille max par image |
| `auto_compress_images` | boolean | true | Compression auto |
| `compress_quality` | integer | 80 | Qualité compression (%) |
| `generate_thumbnails` | boolean | true | Miniatures automatiques |
| `thumbnail_width` | integer | 300 | Largeur miniature (px) |
| `thumbnail_height` | integer | 300 | Hauteur miniature (px) |
| `allowed_image_formats` | string | 'jpg,png,webp' | Formats autorisés |

---

## Initialisation & Valeurs par Défaut

### Création Automatique

Quand un manager active le POS pour la première fois, `PosSettingsService::getOrCreateSettings()` crée une instance `PosSettings` avec toutes les valeurs par défaut.

```php
public function getOrCreateSettings(Company $company): PosSettings
{
    $settings = $this->repository->findOneBy(['company' => $company]);
    
    if (!$settings) {
        $settings = new PosSettings();
        $settings->setCompany($company);
        $settings->setShopName($company->getName());
        // Tous les autres champs ont leur défaut dans l'entité
        $this->entityManager->safePersist($settings);
    }
    
    return $settings;
}
```

### Profils de Configuration Prédéfinis (Futur)

Possibilité d'offrir des profils de démarrage :

| Profil | Description | Particularités |
|--------|-------------|---------------|
| **Boutique** | Commerce de détail classique | Stock actif, cash + mobile money |
| **Restaurant** | Restauration | Pas de stock, services, pas de code-barres |
| **Grossiste** | Vente en gros | Stock critique, bons de commande, crédit client |
| **Pharmacie** | Produits de santé | Stock strict, pas de stock négatif, traçabilité |
| **Personnalisé** | Configuration manuelle | Tous paramètres modifiables |

---

## Validation des Paramètres

### Règles de Validation

```php
// tax_rate : 0-100
Assert\Range(min: 0, max: 100)

// max_discount_percent : 0-100
Assert\Range(min: 0, max: 100)

// decimal_places : 0-4
Assert\Range(min: 0, max: 4)

// auto_logout_minutes : 0-480 (8h max)
Assert\Range(min: 0, max: 480)

// low_stock_threshold > critical_stock_threshold
// (validé par callback)

// credit_limit >= 0
Assert\PositiveOrZero

// accepted_payment_methods : au moins 1 méthode
Assert\Count(min: 1)

// currency : doit être un code ISO 4217 valide
Assert\Choice(choices: ['XOF', 'XAF', 'EUR', 'USD', 'GBP', 'MAD'])
```

### Cohérence entre Paramètres

| Règle | Vérification |
|-------|-------------|
| Crédit client | `allow_credit_sales` = true requiert que `credit` soit dans `accepted_payment_methods` |
| Fidélité | `enable_loyalty` = true requiert que `loyalty` soit dans `accepted_payment_methods` |
| Seuils stock | `low_stock_threshold` > `critical_stock_threshold` |
| Mobile money | Si `mobile_money` dans `accepted_payment_methods`, au moins 1 provider dans `mobile_money_providers` |
| Remise employé | Si `allow_employee_void` = true, `require_employee_pin` devrait être true |

---

## Impact sur l'Interface

### Éléments Conditionnels

| Paramètre | Impact UI |
|-----------|-----------|
| `is_pos_enabled = false` | Section POS masquée dans le sidebar |
| `allow_discount = false` | Bouton remise masqué dans la caisse |
| `require_customer = true` | Client obligatoire avant finalisation |
| `allow_credit_sales = false` | Méthode "Crédit" masquée dans paiement |
| `enable_loyalty = false` | Colonnes fidélité masquées, méthode masquée |
| `require_employee_pin = false` | Pas de modal PIN |
| `enable_negative_stock = false` | Alerte et blocage si stock = 0 |
| `low_stock_alert_enabled = false` | Pas de badge alerte dans sidebar |
| `receipt_show_logo = false` | Logo omis du ticket |

### Formatage Monétaire

```javascript
// Fonction utilitaire côté JS (dans Stimulus)
formatPrice(amount, settings) {
    const formatted = amount.toFixed(settings.decimalPlaces);
    return `${formatted} ${settings.currencySymbol}`;
}

// Exemples :
// XOF : "35400 FCFA" (0 décimales)
// EUR : "35,40 €" (2 décimales)
```

```php
// Fonction utilitaire côté PHP (Twig Extension)
public function formatPosPrice(float $amount, PosSettings $settings): string
{
    return number_format($amount, $settings->getDecimalPlaces(), ',', ' ') 
           . ' ' . $settings->getCurrencySymbol();
}
```
