# Kohinos - AI Code Agent Context & Guidelines

**Last Updated**: 2026-03-07
**Project Version**: Production-ready MLCC platform with SSA and SEPA

---

## 🎯 Quick Reference

- **Language**: PHP 7.4.33
- **Framework**: Symfony 4.4.*
- **Database**: MariaDB (production) / SQLite in-memory (tests)
- **ORM**: Doctrine 2.7
- **Testing**: PHPUnit 9.3+
- **Admin**: Sonata Admin Bundle 3.*
- **API**: API Platform 2.6
- **Frontend**: Bootstrap 5.2.2, jQuery 3.6.1, Webpack Encore
- **Code Style**: PSR-12 (enforced by PHP CS Fixer)
- **Quality Tools**: GrumPHP, PHPStan

---

## 📖 Table of Contents

1. [Project Overview](#project-overview)
2. [Technology Stack](#technology-stack)
3. [Architecture & Patterns](#architecture--patterns)
4. [Project Structure](#project-structure)
5. [Domain Model](#domain-model)
6. [Development Guidelines](#development-guidelines)
7. [Testing Strategy](#testing-strategy)
8. [Key Commands](#key-commands)
9. [Common Patterns & Conventions](#common-patterns--conventions)
10. [Important Business Rules](#important-business-rules)
11. [Troubleshooting](#troubleshooting)
12. [Documentation Index](#documentation-index)

---

## Project Overview

### What is Kohinos?

Kohinos is a **comprehensive local complementary currency management system** (MLCC - Monnaie Locale Complémentaire Citoyenne) that enables communities to create and manage their own local economy.

### Core Features

1. **Electronic Local Currency (eMLc)**
   - Digital currency accounts for members, service providers, and exchange counters
   - Transaction management between all account types
   - Double-entry bookkeeping system
   - Account balance tracking with configurable overdraft limits

2. **SEPA Direct Debit Automation**
   - Recurring MLC subscription requests (cotisations)
   - Recurring purchase requests (achats récurrents)
   - SEPA mandate management with UMR generation
   - Monthly processing with configurable debit days
   - Treasurer validation workflow

3. **SSA (Sécurité Sociale Alimentaire) - Food Security System**
   - Multi-fund support (different SSA programs per region)
   - Monthly taking from adherent purchases at participating prestataires
   - Flexible redistribution strategies (calculated, proportional, fixed)
   - Automated reminders for missing participation
   - Integration with SEPA for automated SSA subscriptions

4. **Payment Integration**
   - HelloAsso API for online payments
   - PayZen gateway support
   - Pending payment tracking with expiration
   - Payment token management

5. **Administrative Features**
   - Sonata Admin interface for all entities
   - Role-based access control (11+ roles)
   - Email notification system
   - Document management
   - Audit logging with Gedmo Loggable
   - Soft delete with Gedmo SoftDeleteable

### Key User Roles

- **ROLE_ADHERENT**: Individual member with eMLc account
- **ROLE_PRESTATAIRE**: Service provider accepting eMLc
- **ROLE_COMPTOIR**: Exchange counter for cash/eMLc conversion
- **ROLE_ADMIN_SIEGE**: Platform administrator
- **ROLE_TRESORIER**: Financial manager for validations
- **ROLE_SECRETAIRE**: Member/provider management
- **ROLE_REDACTEUR**: Content manager
- **ROLE_SOLIDOUME**: SSA fund manager
- **ROLE_SUPER_ADMIN**: Full system access
- **ROLE_CAISSIER**: Counter cashier
- **ROLE_CONTACT**: Limited contact person
- **ROLE_GESTION_GROUPE**: Group manager

---

## Technology Stack

### Backend Core

#### Framework & Language
```yaml
PHP: 7.4.33
Symfony: 4.4.*
Doctrine ORM: 2.7
Doctrine Migrations: 3.1
```

#### Key Bundles
```yaml
# Admin Interface
sonata-project/admin-bundle: 3.*
sonata-project/doctrine-orm-admin-bundle: 3.*
sonata-project/user-bundle: 4.*
sonata-project/media-bundle: 3.*
sonata-project/classification-bundle: 3.*

# API
api-platform/core: 2.6

# User Management
friendsofsymfony/user-bundle: 2.1

# Doctrine Extensions
stof/doctrine-extensions-bundle: (Timestampable, SoftDeleteable, Loggable, Blameable)
gedmo/doctrine-extensions: 2.4

# Forms & Validation
symfony/form: 4.4.*
symfony/validator: 4.4.*

# Utilities
knplabs/knp-paginator-bundle: (pagination)
vich/uploader-bundle: (file uploads)
liip/imagine-bundle: (image manipulation)
```

#### Development Tools
```yaml
# Quality Assurance
phpro/grumphp: (Git hooks for quality checks)
friendsofphp/php-cs-fixer: (PSR-12 code style)
phpstan/phpstan: (Static analysis - Level 6)
phpunit/phpunit: 9.3+ (Testing framework)

# Debugging
symfony/profiler-pack: (Development profiler)
symfony/debug-bundle: (Debug utilities)
```

### Frontend

#### Core Libraries
```json
{
  "bootstrap": "^5.2.2",
  "jquery": "^3.6.1",
  "@popperjs/core": "^2.11.6",
  "select2": "^4.1.0-rc.0",
  "ckeditor": "^4.12.1",
  "chart.js": "^3.4.1",
  "@fortawesome/fontawesome-free": "^6.7.2"
}
```

#### Build Tools
```json
{
  "@symfony/webpack-encore": "^4.0.0",
  "webpack": "^5.74.0",
  "sass": "1.55.0",
  "sass-loader": "13.1.0",
  "workbox-webpack-plugin": "^6.5.4"
}
```

#### PWA Features
- Service Worker (Workbox) for offline functionality
- Web App Manifest
- Push notification support

### Database

- **Production**: MariaDB 10.11.8+ (utf8/utf8_general_ci)
- **Test**: SQLite in-memory
- **Custom DQL Functions**: StrToDate, MONTH, YEAR, ACOS, COS, RADIANS, SIN, DATE

---

## Architecture & Patterns

### Architectural Principles

1. **Service-Oriented Architecture**
   - Business logic in dedicated service classes
   - Controllers are thin, delegating to services
   - Services injected via dependency injection

2. **Repository Pattern**
   - All database queries through custom repositories
   - Named query methods (e.g., `findActiveByAdherent()`)
   - No DQL/QueryBuilder in services or controllers

3. **Domain-Driven Design Elements**
   - Rich domain entities with business logic
   - Value objects for complex types
   - Aggregate roots (Account, Flux, SepaMandate, etc.)

4. **Event-Driven Side Effects**
   - Symfony Event Dispatcher for cross-cutting concerns
   - Event listeners for notifications, logging, audit
   - Decoupled notification system

5. **Strategy Pattern**
   - SSA redistribution strategies (CalculatedSsa, ProportionalSsa, FixedAmountSsa)
   - Payment gateway strategies (HelloAsso, PayZen)
   - Configurable via parameters

### Key Design Patterns

#### Factory Pattern
```php
// src/Factory/FormFactory.php
class FormFactory
{
    public function createRecurringPurchaseForm(Adherent $adherent): FormInterface
    {
        // Centralized form creation with business rules
    }
}
```

#### Service Layer Pattern
```php
// Services orchestrate workflows
class RecurringPurchaseService
{
    public function createPurchaseRequest(
        Adherent $adherent,
        Prestataire $prestataire,
        float $monthlyAmount,
        int $debitDay
    ): RecurringPurchaseRequest {
        // 1. Validate business rules
        // 2. Create entities
        // 3. Persist
        // 4. Dispatch events
        // 5. Return result
    }
}
```

#### Repository Pattern
```php
// src/Repository/RecurringPurchaseRequestRepository.php
class RecurringPurchaseRequestRepository extends ServiceEntityRepository
{
    public function findActiveForProcessing(\DateTime $date): array
    {
        // Named query method, not generic findBy()
    }
}
```

#### Strategy Pattern
```php
// src/Service/Ssa/Strategy/RedistributionStrategyInterface.php
interface RedistributionStrategyInterface
{
    public function calculate(SolidoumeParameter $ssa, array $items): array;
}

// Implementations: CalculatedSsa, ProportionalSsa, FixedAmountSsa
```

---

## Project Structure

### Source Code Organization

```
src/
├── Admin/                  # Sonata Admin classes
│   ├── AdherentAdmin.php
│   ├── PrestataireAdmin.php
│   ├── RecurringPurchaseRequestAdmin.php
│   └── SepaMandateAdmin.php
├── Block/                  # Sonata Blocks for dashboard widgets
├── Command/                # Console commands (SSA, SEPA, accounts)
│   ├── SepaProcessingCommand.php
│   ├── SsaExecuteCommand.php
│   └── AccountCreateCommand.php
├── Controller/             # HTTP Controllers (thin layer)
│   ├── Admin/              # Admin-specific controllers
│   ├── UserAdherentController.php
│   ├── UserComptoirController.php
│   └── SepaController.php
├── Entity/                 # Doctrine entities (domain model)
│   ├── Adherent.php
│   ├── Prestataire.php
│   ├── Account*.php        # Account hierarchy
│   ├── Transaction*.php    # Transaction types
│   ├── Flux.php            # Transaction ledger
│   ├── SepaMandate.php
│   ├── RecurringPurchaseRequest.php
│   ├── RecurringSsaSubscriptionRequest.php
│   └── SolidoumeParameter.php
├── Repository/             # Doctrine repositories with named queries
├── Service/                # Business logic services
│   ├── Sepa/               # SEPA-related services
│   │   ├── SepaMandateService.php
│   │   ├── SepaMandateGenerator.php
│   │   └── SepaNotificationService.php
│   ├── Ssa/                # SSA-related services
│   │   ├── SsaOrchestrator.php
│   │   ├── SsaTakingService.php
│   │   ├── SsaRedistributionService.php
│   │   ├── SsaReminderService.php
│   │   └── Strategy/       # Redistribution strategies
│   ├── Purchase/           # Recurring purchase services
│   └── Subscription/       # Subscription services
├── Form/                   # Form types
│   └── Type/
│       ├── RecurringPurchaseFormType.php
│       └── ModifySubscriptionAmountFormType.php
├── Twig/                   # Twig extensions
│   ├── SepaExtension.php
│   └── FormExtension.php
├── EventListener/          # Event subscribers
├── Factory/                # Object factories
├── Utils/                  # Utility classes
│   └── CustomEntityManager.php
├── Migrations/             # Doctrine migrations
└── Kernel.php
```

### Test Organization

```
tests/
├── Unit/                   # Pure unit tests (no DB)
│   ├── Entity/
│   └── Utils/
├── Service/                # Service layer tests (mocked dependencies)
│   ├── Sepa/
│   │   ├── SepaMandateServiceTest.php
│   │   └── SepaNotificationServiceTest.php
│   ├── Ssa/
│   │   ├── SsaReminderServiceTest.php
│   │   ├── SsaTakingServiceTest.php
│   │   ├── SsaRedistributionServiceTest.php
│   │   └── Strategy/
│   │       └── RedistributionStrategiesTest.php
│   ├── Purchase/
│   └── Subscription/
├── Integration/            # Database integration tests
│   ├── SsaCompleteWorkflowTest.php
│   └── SepaProcessingWorkflowTest.php
├── Functional/             # Full HTTP request/response tests
│   └── Controller/
├── Role/                   # Role-based functional tests
│   ├── AdherentFunctionalTest.php
│   ├── PrestataireFunctionalTest.php
│   ├── SuperAdminFunctionalTest.php
│   └── TresorierFunctionalTest.php
├── Command/                # Console command tests
├── Admin/                  # Sonata Admin tests
├── Form/                   # Form type tests
├── Repository/             # Repository tests
├── AbstractFunctionalTestCase.php  # Base class for functional tests
├── TestDataProvider.php    # Centralized test fixtures
└── bootstrap.php           # Test bootstrap
```

### Configuration Files

```
config/
├── packages/
│   ├── doctrine.yaml       # Database configuration
│   ├── security.yaml       # Security & roles
│   ├── sonata_admin.yaml   # Admin interface
│   ├── api_platform.yaml   # API configuration
│   ├── swiftmailer.yaml    # Email configuration
│   └── test/               # Test-specific overrides
│       └── doctrine.yaml   # SQLite for tests
├── routes/
│   ├── annotations.yaml
│   └── sonata_admin.yaml
└── services.yaml           # Service container configuration
```

---

## Domain Model

### Core Entities

#### User & Account Hierarchy

```
User (FOSUserBundle)
├── roles[]
├── enabled
└── getProfile() → Adherent|Prestataire|Comptoir

Account (abstract)
├── balance
├── owner (Adherent|Prestataire|Comptoir|Groupe|Siege)
└── Implementations:
    ├── AccountAdherent
    ├── AccountPrestataire
    ├── AccountComptoir
    ├── AccountGroupe
    └── AccountSiege
```

#### Transaction Hierarchy

```
Transaction (abstract)
├── montant (amount)
├── reference
├── moyen (payment method)
├── createdAt
├── expediteur (sender)
└── destinataire (recipient)

Flux (transaction ledger)
├── type (transaction type)
├── adherent_id
├── prestataire_id
├── solidoume_parameter_id (for SSA transactions)
└── reference (important for SSA: starts with "Solidoume")

Transaction Types:
├── TransactionAdherentPrestataire       (payment)
├── TransactionAdherentAdherent          (transfer)
├── TransactionPrestataireAdherent       (refund)
├── TransactionAdherentPrestataireSsa    (SSA taking)
├── TransactionAdherentEmlctoSsa         (SSA direct payment)
└── TransactionPrestataireAdherentSsa    (SSA redistribution)
```

#### SEPA Entities

```
SepaMandate
├── adherent|prestataire
├── iban
├── bic
├── accountHolderName
├── mandateReference (UMR - unique mandate reference)
├── status (pending, active, cancelled, inactive)
└── validatedAt

AbstractRecurringRequest (abstract)
├── adherent|prestataire
├── sepaMandate
├── status (pending, validated, active, cancelled, suspended)
├── createdAt
├── validatedAt
└── Implementations:
    ├── RecurringMlcSubscriptionRequest  (annual MLC subscription)
    ├── RecurringPurchaseRequest         (monthly purchases)
    └── RecurringSsaSubscriptionRequest  (SSA participation)

RecurringPurchaseRequest extends AbstractRecurringRequest
├── prestataire
├── monthlyAmount
├── debitDay (1-28)
├── lastProcessedMonth (YYYYMM format)
├── newAmountRequested (for modifications)
└── amountApprovedAt

RecurringSsaSubscriptionRequest extends AbstractRecurringRequest
├── solidoumeParameter (SSA fund)
├── amount
└── debitDay
```

#### SSA Entities

```
SolidoumeParameter (SSA Fund)
├── name
├── redistributionStrategy (calculated|proportional|fixed)
├── tauxPrelevement (taking percentage)
├── enabled
└── prestataires[] (participating service providers)

SolidoumeItem (SSA Participant)
├── adherent
├── solidoumeParameter
├── montantCalcule (calculated amount)
├── remboursementFait (redistribution done)
├── month/year
└── isPayed (has participated this month)

PrestataireSolidoumeParameter (many-to-many)
├── prestataire
├── solidoumeParameter
└── enabled (can participant buy from this prestataire)
```

### Important Entity Relationships

```
Adherent
  ├── 1:1  → AccountAdherent
  ├── 1:n  → TransactionAdherentPrestataire (as expediteur)
  ├── 1:n  → SolidoumeItem (SSA participation)
  ├── 0:n  → SepaMandate
  └── 0:n  → RecurringSsaSubscriptionRequest

Prestataire
  ├── 1:1  → AccountPrestataire
  ├── 1:n  → TransactionAdherentPrestataire (as destinataire)
  ├── n:m  → SolidoumeParameter (via PrestataireSolidoumeParameter)
  └── 0:n  → RecurringPurchaseRequest

SolidoumeParameter (SSA)
  ├── n:m  → Prestataire (participating providers)
  ├── 1:n  → SolidoumeItem (participants)
  └── 1:n  → RecurringSsaSubscriptionRequest
```

---

## Development Guidelines

### Frontend JavaScript Rules

#### ✅ RULE: Always Use console.log() for Debugging

**ALWAYS** add `console.log()` statements in JavaScript files to facilitate debugging.
These logs are **automatically removed in production** by the Webpack Encore Terser plugin (`drop_console: true` in `webpack.config.js`).

```js
// Use a prefixed format for easy identification:
console.log('[FeatureName] Description of what happened:', variable);

// Examples:
console.log('[BofficeList] Filtre appliqué - type:', filterType, 'priority:', filterPriority);
console.log('[BofficeKanban] Colonnes chargées:', columns.length);
console.log('[SepaMandate] Sélection propriétaire:', ownerId);
```

**Convention**:
- Prefix with `[ModuleName]` in brackets
- Log key events: init, data loaded, user actions (filter/click), errors
- Log relevant variable values alongside the message
- Never use `console.warn` or `console.error` for debug — use `console.log` only (all are stripped in prod)

#### ⛔ RULE: No Inline JavaScript OR CSS in Any Twig Template

**NEVER** put JavaScript or CSS code directly in `.html.twig` files — this applies to **all templates without exception**, including:
- Frontend templates
- **Sonata Admin templates** (overrides, blocks, list/show/edit views)
- Email templates

❌ **Forbidden** — JS in template:
```twig
{% block javascripts %}
    {{ parent() }}
    <script>
        var myVar = {{ someValue }};
        // ... any JS logic ...
    </script>
{% endblock %}
```

❌ **Forbidden** — CSS in template (including Sonata Admin overrides):
```twig
{% block stylesheets %}
    {{ parent() }}
    <style>
        .my-class { color: red; }
    </style>
{% endblock %}
```

❌ **Forbidden** — CDN links:
```twig
<script src="https://cdn.example.com/lib.js"></script>
<link rel="stylesheet" href="https://cdn.example.com/lib.css">
```

✅ **Correct pattern** — always use dedicated files compiled by Webpack Encore:

1. Create `assets/js/my-feature.js` and/or `assets/css/my-feature.css`
2. Pass data via `data-*` attributes on HTML elements (never via inline JS variables)
3. Register the entry in `webpack.config.js`: `.addEntry('my-feature', './assets/js/my-feature.js')`
4. Include in the Twig template with: `{{ encore_entry_script_tags('my-feature') }}`

```js
// assets/js/my-feature.js
document.addEventListener('DOMContentLoaded', function () {
    const el = document.getElementById('my-element');
    if (!el) return;
    const value = parseInt(el.getAttribute('data-value'), 10);
    // ... logic ...
});
```

```twig
{# Pass data via data-* attributes #}
<div id="my-element" data-value="{{ someValue }}" style="display:none;"></div>

{% block javascripts %}
    {{ parent() }}
    {{ encore_entry_script_tags('my-feature') }}
{% endblock %}
```

**Special case — Sonata Admin templates**: If you need to add JS/CSS to an admin view, create a dedicated entry (e.g., `admin-my-feature`) and include it via the standard Webpack Encore mechanism in the overridden block. Never use Sonata's `extra_javascripts` or `extra_stylesheets` with external URLs.

This rule applies to **all** Twig templates: admin, frontend, email, etc.
CDN `<script src="...">` tags are also forbidden — use npm packages instead.

---

#### ⚡ RULE: Vanilla JS First — Migration jQuery vers JS natif

**Toujours préférer le JavaScript natif (ES2020+) à jQuery.**

Le projet a jQuery en dépendance pour des raisons historiques. La stratégie est de migrer progressivement :

> **Règle de migration** : Dès qu'on touche à un fichier JS existant pour n'importe quelle raison, on migre **l'intégralité de ce fichier** vers du JS natif — tout en conservant un comportement identique à 100%.

**Équivalences jQuery → JS natif** :

```js
// ❌ jQuery                          ✅ JS natif
$('#id')                           → document.getElementById('id')
$('.class')                        → document.querySelectorAll('.class')
$(el).on('click', fn)             → el.addEventListener('click', fn)
$(el).addClass('foo')             → el.classList.add('foo')
$(el).removeClass('foo')          → el.classList.remove('foo')
$(el).hasClass('foo')             → el.classList.contains('foo')
$(el).attr('data-x')              → el.getAttribute('data-x')
$(el).data('x')                   → el.dataset.x
$(el).val()                       → el.value
$(el).text()                      → el.textContent
$(el).html()                      → el.innerHTML
$(el).show() / .hide()            → el.style.display = '' / 'none'
$(el).closest('.parent')          → el.closest('.parent')
$(el).find('.child')              → el.querySelectorAll('.child')
$(el).parent()                    → el.parentElement
$(el).append(child)               → el.append(child)
$(el).remove()                    → el.remove()
$.ajax({ url, success })          → fetch(url).then(r => r.json()).then(...)
$(document).ready(fn)             → document.addEventListener('DOMContentLoaded', fn)
$.each(arr, fn)                   → arr.forEach(fn)
```

**Pattern fetch (remplace $.ajax)** :
```js
// ✅ JS natif — remplace $.ajax
async function loadData(url) {
    try {
        const response = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        const data = await response.json();
        return data;
    } catch (err) {
        console.log('[Feature] Erreur fetch:', err);
        return null;
    }
}
```

**Exceptions** : Si une bibliothèque tierce (ex. Select2, CKEditor) requiert jQuery comme peer dependency, on ne la migre pas — on garde jQuery dans le scope minimal nécessaire.

---

### CHANGELOG Requirements

**TOUJOURS** mettre à jour les deux fichiers de changelog à chaque développement, avant de committer.

#### Les deux fichiers et leur rôle

| Fichier | Public | Contenu |
|---------|--------|---------|
| `CHANGELOG.md` | Développeurs | Technique : classes modifiées, migrations, patterns, breaking changes |
| `CHANGELOG_USER.md` | Utilisateurs & admins | Fonctionnel : ce qui change visuellement, nouvelles fonctionnalités, comportements |

#### Format CHANGELOG.md (technique)

```markdown
# vX.Y.Z (YYYY-MM-DD)

## feat|fix|refactor|perf|chore(scope) — Titre court

Description courte du changement.

### Changements

- **`ClassName`** : ce qui a changé
- **Migration** : `VersionXXXXXX` — description de la migration SQL
- **Tests** : fichiers de test ajoutés/modifiés
```

#### Format CHANGELOG_USER.md (fonctionnel)

```markdown
# 🆕 vX.Y.Z — Mois YYYY

## 🔔 Fonctionnalité — Titre utilisateur

### Pour les [adhérents|prestataires|administrateurs]

Description simple, sans jargon technique.

> Note pratique ou avertissement si nécessaire.
```

#### Règles

- **Toujours en tête de fichier** (versions les plus récentes en premier)
- `CHANGELOG_USER.md` : pas de noms de classes PHP, pas de SQL — seulement l'impact fonctionnel visible
- Si le changement est purement technique (refactoring interne, test, CI) : entrée dans `CHANGELOG.md` uniquement, pas dans `CHANGELOG_USER.md`
- Si le changement n'est pas visible des utilisateurs : `CHANGELOG_USER.md` peut être omis ou réduit à une ligne

---

### Code Style & Standards

#### PHP Standards (PSR-12)

**ALWAYS** use:
- Strict types: `declare(strict_types=1);`
- Type hints for all parameters and return types
- Visibility keywords (public, private, protected)
- Final classes when not designed for inheritance

```php
<?php

declare(strict_types=1);

namespace App\Service\Ssa;

use App\Entity\SolidoumeParameter;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;

final class SsaReminderService
{
    public function __construct(
        private EntityManagerInterface $em,
        private LoggerInterface $logger
    ) {}

    public function executeReminders(SolidoumeParameter $ssa): array
    {
        // Implementation
    }
}
```

#### Naming Conventions

**Classes**:
- PascalCase
- Service suffix for services: `SsaTakingService`
- Repository suffix: `RecurringPurchaseRequestRepository`
- Controller suffix: `UserAdherentController`
- Admin suffix: `SepaMandateAdmin`

**Methods**:
- camelCase
- Verbs for actions: `createPurchaseRequest()`, `validateSepaMandate()`
- `get`/`set` for accessors
- `find` prefix for repository queries: `findActiveForProcessing()`
- `is`/`has`/`can` for booleans: `isMainSsa()`, `hasModificationRequest()`, `canBeModified()`

**Constants**:
- UPPER_SNAKE_CASE
- Entity status constants: `STATUS_PENDING`, `STATUS_ACTIVE`, `STATUS_CANCELLED`
- Types: `TYPE_TRANSACTION_ADHERENT_PRESTATAIRE`

### Business Logic Rules

#### Rule 1: No Magic Numbers or Strings

❌ **Incorrect**:
```php
if ($request->getStatus() === 'active') {
    // ...
}

if ($debitDay > 28) {
    throw new \Exception('Invalid debit day');
}
```

✅ **Correct**:
```php
if ($request->getStatus() === RecurringPurchaseRequest::STATUS_ACTIVE) {
    // ...
}

if ($debitDay > GlobalParameter::MAX_DEBIT_DAY) {
    throw new InvalidDebitDayException('Debit day must be between 1-28');
}
```

#### Rule 2: Services Orchestrate, Entities Contain Logic

✅ **Correct**:
```php
// Service orchestrates
class RecurringPurchaseService
{
    public function createPurchaseRequest(/* params */): RecurringPurchaseRequest
    {
        // 1. Validate business rules
        $this->validateEligibility($adherent);
        $this->validateSepaMandate($sepaMandate);

        // 2. Create entity (entity knows its own business logic)
        $request = RecurringPurchaseRequest::createForAdherent(
            $adherent,
            $prestataire,
            $monthlyAmount,
            $debitDay
        );

        // 3. Persist
        $this->em->persist($request);
        $this->em->flush();

        // 4. Side effects (notifications)
        $this->notificationService->notifyNewPurchaseRequest($request);

        return $request;
    }
}

// Entity contains domain logic
class RecurringPurchaseRequest
{
    public function hasModificationRequest(): bool
    {
        return $this->newAmountRequested !== null;
    }

    public function canBeModified(): bool
    {
        return $this->status === self::STATUS_VALIDATED
            && !$this->hasModificationRequest();
    }
}
```

#### Rule 3: Repository Pattern - Named Query Methods

✅ **Correct**:
```php
class RecurringPurchaseRequestRepository extends ServiceEntityRepository
{
    public function findActiveForProcessing(\DateTime $date): array
    {
        $monthKey = $date->format('Ym');

        return $this->createQueryBuilder('r')
            ->where('r.status = :active')
            ->andWhere('r.debitDay <= :day')
            ->andWhere('r.lastProcessedMonth != :month OR r.lastProcessedMonth IS NULL')
            ->setParameter('active', RecurringPurchaseRequest::STATUS_ACTIVE)
            ->setParameter('day', (int) $date->format('d'))
            ->setParameter('month', $monthKey)
            ->orderBy('r.adherent', 'ASC')
            ->getQuery()
            ->getResult();
    }
}
```

#### Rule 4: Explicit State Machines

For entities with state transitions (SEPA mandates, recurring requests), use explicit methods:

```php
class SepaMandate
{
    public function validate(string $mandateReference): void
    {
        ...
    }

    public function cancel(): void
    {
        ...
    }
}
```

#### Rule 5: Double-Flush Pattern for UUID-Based References

When generating references that depend on the entity's UUID, use the double-flush pattern:

❌ **Incorrect** (UUID not yet available):
```php
$mandate = new SepaMandate();
$mandate->setIban($iban);
$mandate->setMandateReference($this->generateMandateReference($mandate)); // ID is null!
$this->em->persist($mandate);
$this->em->flush();
```

✅ **Correct** (persist first to get UUID):
```php
$mandate = new SepaMandate();
$mandate->setIban($iban);
// ... set other properties

// Persist first to get the UUID
$this->em->persist($mandate);
$this->em->flush();

// Generate RUM after persist/flush so we have the ID
$mandate->setMandateReference($this->generateMandateReference($mandate));
$this->em->flush(); // Second flush to save the reference
```

**Why?** The `generateMandateReference()` method needs the entity's UUID to create a unique reference in format `KOHINOS-YYYYMMDD-XXXXXXXX`. The UUID is only available after the first flush.

### Dependency Injection

**ALWAYS** use constructor injection:

```php
class SsaOrchestrator
{
    public function __construct(
        private SsaTakingService $takingService,
        private SsaRedistributionService $redistributionService,
        private SsaReminderService $reminderService
    ) {}
}
```

### Error Handling

1. **Use specific exceptions** for business rule violations
2. **Log errors** with context
3. **Catch at controller level** and show user-friendly messages

```php
try {
    $request = $this->purchaseService->createPurchaseRequest($adherent, $prestataire, $amount, $debitDay);
    $this->addFlash('success', 'Purchase request created successfully');
} catch (InvalidDebitDayException $e) {
    $this->addFlash('error', $e->getMessage());
} catch (\Exception $e) {
    $this->logger->error('Failed to create purchase request', [
        'adherent' => $adherent->getId(),
        'error' => $e->getMessage(),
    ]);
    $this->addFlash('error', 'An error occurred. Please try again.');
}
```

---

## Testing Strategy

### Test Organization

#### Test Suites (phpunit.xml.dist)

```xml
<!-- Run by feature -->
<testsuite name="ssa">
  <directory>tests/Service/Ssa</directory>
</testsuite>

<testsuite name="sepa">
  <directory>tests/Service/Sepa</directory>
</testsuite>

<!-- Run by type -->
<testsuite name="unit">
  <directory>tests/Service</directory>
  <directory>tests/Form</directory>
</testsuite>

<testsuite name="functional">
  <directory>tests/Controller</directory>
  <directory>tests/Role</directory>
</testsuite>
```

### Test Database Setup

**CRITICAL**: Tests use a separate SQLite in-memory database configured in `config/packages/test/doctrine.yaml`.

Initialize test environment:
```bash
# Automated script (recommended)
./scripts/setup-test-environment.sh

# Or manual steps
php bin/console doctrine:database:create --env=test
php bin/console doctrine:migrations:migrate --env=test --no-interaction
php bin/console app:init-test-database --env=test --purge
```

### Test Structure

#### Centralized Test Fixtures

Use `TestDataProvider` for creating test entities:

#### AbstractFunctionalTestCase

For functional tests, extend `AbstractFunctionalTestCase`:

### Critical Testing Patterns

#### 1. SSA Transaction Tests - Reference Pattern

**IMPORTANT**: For SSA transactions to be found by repository queries:
- Reference MUST start with "Solidoume"
- Transaction MUST have `solidoume_parameter_id` set

```php
private function createSsaPrelevement(
    Adherent $adherent,
    Prestataire $prestataire,
    float $amount,
    SolidoumeParameter $ssa
): TransactionAdherentPrestataire {
    $transaction = new TransactionAdherentPrestataire();
    $transaction->setExpediteur($adherent);
    $transaction->setDestinataire($prestataire);
    $transaction->setMontant($amount);
    $transaction->setReference('Solidoume Test'); // MUST start with "Solidoume"
    $transaction->setSolidoumeParameter($ssa);    // MUST set SSA parameter
    $transaction->setMoyen('cb');
    $transaction->setCreatedAt(new \DateTime());

    $this->em->persist($transaction);
    return $transaction;
}
```

**Why?** The `FluxRepository::getQueryBuilderByAdherentAndDestinataire()` method filters SSA transactions with:
```php
if ($ssa->isMainSsa()) {
    $sqlQuery .= ' AND f.reference LIKE \'Solidoume %\'';
}
```

#### 2. Mocking in Service Tests

Mock external dependencies, use real domain objects:

```php
class SsaReminderServiceTest extends TestCase
{
    private SsaReminderService $service;
    private EntityManagerInterface $em;     // Mocked
    private MailerInterface $mailer;         // Mocked

    protected function setUp(): void
    {
        // Mock infrastructure
        $this->em = $this->createMock(EntityManagerInterface::class);
        $this->mailer = $this->createMock(MailerInterface::class);

        // Real service under test
        $this->service = new SsaReminderService($this->em, $this->mailer);
    }

    public function testExecuteRemindersWithEmptyItems(): void
    {
        // Use real domain objects
        $ssa = new SolidoumeParameter();
        $ssa->setName('Test SSA');

        // Mock repository returns empty array
        $repo = $this->createMock(EntityRepository::class);
        $repo->method('findBy')->willReturn([]);
        $this->em->method('getRepository')->willReturn($repo);

        $results = $this->service->executeReminders($ssa);

        $this->assertEquals(0, $results['reminders_sent']);
    }
}
```

### Test Naming

Pattern: `test{Action}{ExpectedResult}[Context]`

✅ **Good**:
- `testAdherentCanSubscribeToSsaWithValidAmount()`
- `testSepaMandateRequiresTreasurerValidation()`

❌ **Bad**:
- `testSubscribe()` - Too vague
- `testIt()` - Not descriptive

---

## Key Commands

### Custom Kohinos Commands

```bash
# Account Management
php bin/console kohinos:accounts:create
php bin/console kohinos:accounts:check

# SSA Processing
php bin/console kohinos:ssa:execute                       # Execute SSA (refactored version)
php bin/console kohinos:ssa:execute --test                # Simulation mode
php bin/console kohinos:ssa:execute --execute --test      # Simulate redistribution only
php bin/console kohinos:solidoume:execute                 # Legacy SSA command
php bin/console kohinos:ssa:send-former-participant-alerts           # Send reminders to former participants
php bin/console kohinos:ssa:send-former-participant-alerts --test    # Dry run

# SEPA Processing
php bin/console kohinos:sepa:process-monthly              # Process monthly SEPA debits
php bin/console kohinos:sepa:process-monthly --test       # Dry run
php bin/console kohinos:sepa:process-monthly --force-date=2025-01-15

# MLC Subscriptions
php bin/console kohinos:mlc:process-annual-subscriptions  # Process annual MLC subscriptions
```

### Doctrine Commands

```bash
# Database Management
php bin/console doctrine:database:create
php bin/console doctrine:database:drop --force
php bin/console doctrine:migrations:migrate
php bin/console doctrine:migrations:diff

# Schema Management
php bin/console doctrine:schema:validate
php bin/console doctrine:schema:update --dump-sql
```

### Testing Commands

```bash
# Run all tests
vendor/bin/phpunit

# Run specific suite
vendor/bin/phpunit --testsuite=ssa
vendor/bin/phpunit --testsuite=sepa
vendor/bin/phpunit --testsuite=unit
vendor/bin/phpunit --testsuite=functional

# Run specific test file
vendor/bin/phpunit tests/Service/Ssa/SsaReminderServiceTest.php

# Run with testdox (readable output)
vendor/bin/phpunit --testdox

# Run with coverage
vendor/bin/phpunit --coverage-html coverage/
```

### Quality Checks

```bash
# Run all quality checks (GrumPHP)
vendor/bin/grumphp run

# PHP CS Fixer
vendor/bin/php-cs-fixer fix                    # Fix all files
vendor/bin/php-cs-fixer fix --dry-run          # Check without fixing
vendor/bin/php-cs-fixer fix src/Service/Ssa    # Fix specific directory

# PHPStan
vendor/bin/phpstan analyze src tests

# YAML Lint
php bin/console lint:yaml config
```

### Frontend Build

> **IMPORTANT (local dev)**: Always use `npm run dev` to compile assets locally.
> Never use `npm run build` in local/dev environment — it is reserved for production CI/CD.

```bash
# Development
npm run dev              # Build for development — USE THIS LOCALLY
npm run watch            # Watch for changes

# Production (CI/CD only)
npm run build            # Build for production (optimized) — DO NOT USE LOCALLY

# Cleanup
npm run clean            # Remove node_modules and public/build
npm run reinstall        # Clean + fresh install
```

### Cache Management

```bash
php bin/console cache:clear                    # Clear cache
php bin/console cache:clear --env=prod         # Clear production cache
php bin/console cache:warmup                   # Warmup cache
```

---

## Common Patterns & Conventions

### SSA (Solidoume) Processing Flow

1. **Taking**: 5% (configurable) of adherent purchases at participating prestataires
2. **Redistribution**: Based on strategy configured in SolidoumeParameter
3. **Reference Pattern**: SSA transactions MUST have reference starting with "Solidoume"
4. **Multi-Fund**: Multiple SSA funds can coexist with different strategies

### SEPA Processing Flow

1. **Debit Day**: Configurable per request (1-28 for February safety)
2. **Monthly Tracking**: `lastProcessedMonth` in YYYYMM format prevents double processing
3. **Validation Flow**: Pending → Validated (Treasurer) → Active
4. **Mandate Reference**: UMR (Unique Mandate Reference) generated on creation after first flush
5. **Mandate Cancellation**: Comprehensive cancellation process that automatically cancels all linked recurring requests

**Mandate Cancellation Code Example**:
```php
// Get all recurring requests linked to a mandate
$requests = $sepaMandateService->getRecurringRequestsForMandate($mandate);
// Returns: ['mlcSubscriptions' => [], 'ssaSubscriptions' => [], 'purchases' => [], 'total' => 0]

// Cancel mandate and all associated recurring requests atomically
$result = $sepaMandateService->cancelMandateWithRecurringRequests($mandate);
// Returns: [
//   'mandateCancelled' => true,
//   'mlcSubscriptionsCancelled' => 2,
//   'ssaSubscriptionsCancelled' => 1,
//   'purchasesCancelled' => 3,
//   'totalCancelled' => 6
// ]
```

### Transaction Reference Patterns

**IMPORTANT**: Reference field is used for filtering and identifying transaction types.

| Transaction Type | Reference Pattern | Example |
|-----------------|-------------------|---------|
| SSA Taking | `Solidoume prélèvement {month}` | "Solidoume prélèvement 01/2025" |
| SSA Redistribution | `Solidoume {month}` | "Solidoume 01/2025" |
| SSA Subscription | `Solidoume cotisation {month}` | "Solidoume cotisation 01/2025" |
| SEPA Purchase | `Achat récurrent {month}` | "Achat récurrent 01/2025" |
| SEPA Subscription | `Cotisation MLC {year}` | "Cotisation MLC 2025" |
| Manual Payment | Custom | "Paiement comptoir" |

**Repository Query Pattern**:
```php
// FluxRepository checks reference for SSA transactions
if ($ssa->isMainSsa()) {
    $sqlQuery .= ' AND f.reference LIKE \'Solidoume %\'';
}
```

### Account Balance Management

Kohinos uses **double-entry bookkeeping**:

1. **Operations**: Individual debit/credit entries
   - `OperationAdherent`, `OperationPrestataire`, etc.
   - Each operation has `amount` and `sens` (debit/credit)

2. **Flux**: Transaction ledger linking operations
   - Tracks all money movement
   - Links expediteur → destinataire
   - Used for reporting and SSA calculation

3. **Account Balance**: Calculated from operations
   ```php
   // Account balance = sum(credits) - sum(debits)
   $balance = $account->getBalance();
   ```

### Email Notifications

Email system is centralized in:
- `FluxNotificationService`: Transaction notifications
- `SepaNotificationService`: SEPA-specific emails
- Template location: `templates/themes/kohinos/email/`

**Key Templates**:
```
email/
├── sepa/
│   ├── mandate_inactive_report_treasurer.html.twig
│   ├── treasurer_new_mandate.html.twig
│   ├── treasurer_new_purchase_request.html.twig
│   ├── modification_rejected.html.twig
│   └── iban_updated.html.twig
├── flux/
│   └── transaction_notification.html.twig
└── base_email.html.twig
```

### Soft Delete Pattern

Entities use Gedmo SoftDeleteable:

```php
use Gedmo\Mapping\Annotation as Gedmo;

/**
 * @ORM\Entity
 * @Gedmo\SoftDeleteable(fieldName="deletedAt")
 */
class Adherent
{
    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    private ?\DateTime $deletedAt = null;
}
```

**Important**: Soft-deleted entities are automatically filtered in queries unless explicitly included.

---

## Important Business Rules

### SSA (Solidoume) Rules

1. **Participation Requirement**
   - Adherent must make at least one purchase at SSA prestataire during the month
   - Non-participants receive reminders

2. **Taking Calculation**
   - Default: 5% of purchases at participating prestataires
   - Configurable per SSA fund via `tauxPrelevement`

3. **Redistribution Strategies**
   - **Calculated**: Based on household size and participation
   - **Proportional**: Proportional to participation amount
   - **Fixed Amount**: Same amount to all participants

4. **Main SSA vs Regional SSA**
   - Main SSA: `isMainSsa() === true`, identified by reference pattern
   - Regional SSA: Linked to specific prestataires via `PrestataireSolidoumeParameter`

5. **Multi-Fund Support**
   - Multiple SSA funds can coexist
   - Each fund has own strategy and participating prestataires
   - Adherent can participate in multiple funds

### SEPA Rules

1. **Mandate Validation**
   - All SEPA mandates require treasurer validation
   - UMR (Unique Mandate Reference) generated on creation (after first flush to get UUID)
   - Format: `KOHINOS-YYYYMMDD-XXXXXXXX` (where X is first 8 chars of UUID without dashes)
   - Example: `KOHINOS-20251215-A3B4C5D6`

2. **Debit Day Constraints**
   - Must be between 1-28 (to handle February)
   - Configurable per recurring request
   - Cannot be changed while modification is pending

3. **Monthly Processing**
   - Each recurring request tracked with `lastProcessedMonth` (YYYYMM format)
   - Prevents double processing in same month
   - Processes only active requests with matching debit day

4. **Modification Workflow**
   - User requests modification (new amount or debit day)
   - Treasurer approves/rejects
   - Cannot have multiple pending modifications

5. **IBAN Changes**
   - Require new mandate creation
   - Previous mandate marked as inactive
   - Notifications sent to user and treasurer

6. **Mandate Cancellation**
   - When a mandate is cancelled, ALL associated recurring requests are automatically cancelled
   - This includes: MLC subscriptions, SSA subscriptions, and recurring purchases
   - User sees a comprehensive modal listing all affected payments before cancellation
   - Cancellation is atomic - either all succeed or all fail
   - Detailed feedback provided: counts of each type of recurring request cancelled
   - Use `cancelMandateWithRecurringRequests()` instead of `cancelMandate()` for proper cleanup

### Account Rules

1. **Overdraft Limits**
   - Configured in `GlobalParameter`
   - Different limits for adherents vs prestataires
   - Enforced at transaction creation

2. **Balance Calculation**
   - Real-time from operation entries
   - No cached balance field (integrity)

3. **Account Creation**
   - Automatic on user registration (Adherent/Prestataire)
   - Manual for Comptoir/Groupe via `kohinos:accounts:create`

### Transaction Rules

1. **Validation Before Creation**
   - Check sender balance (including overdraft)
   - Verify recipient account exists
   - Check business rules (SSA participation, SEPA mandate active, etc.)

2. **Atomic Operations**
   - Transaction + Operations created in single database transaction
   - Use `CustomEntityManager::transactional()` for retryable operations

3. **Reference Format**
   - Must follow pattern for SSA transactions (starts with "Solidoume")
   - Used for reporting and filtering

---

## Troubleshooting

### Common Issues

#### 1. SSA Redistribution Returns 0 Amounts

**Symptoms**: Tests or SSA command shows 0.0 for calculated amounts

**Causes**:
- Transactions missing `solidoume_parameter_id`
- Reference doesn't start with "Solidoume"
- `FluxRepository` query can't find transactions

**Solution**:
```php
// Ensure SSA transactions are properly created
$transaction->setReference('Solidoume Test');
$transaction->setSolidoumeParameter($ssa);
```

**Migration for Historical Data**:
```bash
# Run migration to fix existing data
php bin/console doctrine:migrations:migrate
# See: src/Migrations/Version20251126000000.php
```

#### 2. SEPA Requests Processed Multiple Times

**Symptoms**: Same debit applied twice in one month

**Cause**: `lastProcessedMonth` not updated correctly

**Solution**:
```php
// Always update lastProcessedMonth after processing
$request->setLastProcessedMonth($date->format('Ym'));
$this->em->flush();
```

#### 3. Test Database Not Initialized

**Symptoms**: Tests fail with "Table not found" errors

**Solution**:
```bash
# Run setup script
./scripts/setup-test-environment.sh

# Or manually
php bin/console doctrine:database:drop --force --env=test
php bin/console doctrine:database:create --env=test
php bin/console doctrine:migrations:migrate --env=test --no-interaction
php bin/console app:init-test-database --env=test --purge
```

#### 4. GrumPHP Pre-Commit Failures

**Symptoms**: Git commit blocked by quality checks

**Solutions**:
```bash
# Fix code style
vendor/bin/php-cs-fixer fix

# Run checks manually
vendor/bin/grumphp run

# Bypass (emergency only)
git commit --no-verify
```

#### 5. Asset Build Failures

**Symptoms**: Webpack Encore errors

**Solutions**:
```bash
# Clean reinstall
npm run clean
npm install

# Check Node/NPM versions
node -v  # Should be >= 18.0.0
npm -v   # Should be >= 9.0.0

# Rebuild
npm run dev
```

### Debugging Tips

#### 1. Database Queries

Enable Doctrine SQL logging in development:

```yaml
# config/packages/dev/doctrine.yaml
doctrine:
    dbal:
        logging: true
        profiling: true
```

View queries in Symfony Profiler Web Debug Toolbar.

#### 2. Service Container

List all services:
```bash
php bin/console debug:container
php bin/console debug:container --show-private
php bin/console debug:container SsaOrchestratorService
```

#### 3. Routes

Debug routes:
```bash
php bin/console debug:router
php bin/console debug:router app_ssa_subscribe
```

#### 4. Events

List event listeners:
```bash
php bin/console debug:event-dispatcher
```

---

## Documentation Index

### Core Documentation

- [README.md](README.md) - Project overview and quick start
- [INSTALL.md](INSTALL.md) - Installation guide
- [SPECS.md](SPECS.md) - Technical specifications and business logic
- [CLAUDE.md](CLAUDE.md) - Development guide and rules for AI agents
- [CHANGELOG.md](CHANGELOG.md) - Version history
- [DOCUMENTATION.md](DOCUMENTATION.md) - Complete documentation index

### Feature Documentation

#### SEPA System
- [docs/sepa/](docs/sepa/) - SEPA system documentation

#### SSA System
- [docs/ssa/SSA_REFACTORING_REPORT.md](docs/ssa/SSA_REFACTORING_REPORT.md) - SSA refactoring details
- [docs/ssa/SSA_CONTROLLER_REFACTORING_SUMMARY.md](docs/ssa/SSA_CONTROLLER_REFACTORING_SUMMARY.md)
- [docs/ssa/SSA_COMMAND_COTISATION_FIX.md](docs/ssa/SSA_COMMAND_COTISATION_FIX.md)

#### Testing
- [tests/README.md](tests/README.md) - Test setup and organization
- [tests/TESTING_GUIDE.md](tests/TESTING_GUIDE.md) - Testing best practices
- [docs/testing/TESTS_QUICK_REFERENCE.md](docs/testing/TESTS_QUICK_REFERENCE.md)

#### HelloAsso Integration
- [docs/helloasso/](docs/helloasso/) - HelloAsso payment integration

#### CI/CD & Deployment
- [docs/ci-cd/DEPLOIEMENT_PRODUCTION_GUIDE.md](docs/ci-cd/DEPLOIEMENT_PRODUCTION_GUIDE.md)
- [docs/ci-cd/GITLAB_CI_DOCUMENTATION.md](docs/ci-cd/GITLAB_CI_DOCUMENTATION.md)

#### Email System
- [docs/email/EMAIL_SYSTEM_REDESIGN.md](docs/email/EMAIL_SYSTEM_REDESIGN.md)
- [docs/email/EMAIL_DIAGNOSTIC_GUIDE.md](docs/email/EMAIL_DIAGNOSTIC_GUIDE.md)

### Installation & Setup
- [docs/installation/](docs/installation/) - Installation guides and database setup

---

## Boffice Module (SAV — Ticket Management)

### Overview

The Boffice module is an embedded ticket management system (SAV) integrated into the Kohinos admin panel. It communicates with an **external Boffice API** via a server-side proxy (`BofficeController` + `BofficeApiClient`).

### Key files

| File | Role |
|------|------|
| `src/Controller/BofficeController.php` | Routes + API proxy (thin controller) |
| `src/Service/BofficeApiClient.php` | HTTP client, caching, cache invalidation |
| `templates/themes/kohinos/admin/boffice/layout.html.twig` | Shared layout, modals (create + edit), JS logic |
| `assets/js/boffice-api.js` | jQuery wrapper, rich ticket modal, helpers |
| `assets/js/boffice-ticket-show.js` | Ticket detail page (vanilla JS ES2020) |
| `assets/js/boffice-ticket-form.js` | Stub (no-op) — logic is in layout.html.twig |
| `tests/Unit/Service/BofficeApiClientCacheTest.php` | Cache TTL + invalidation unit tests |
| `tests/Unit/Service/BofficeTicketFieldsTest.php` | Separate bug fields unit tests |

### API version: v2.1.0

#### Ticket object (received from API — camelCase)

```json
{
  "id": 42,
  "type": "bug",
  "title": "...",
  "description": null,
  "stepsToReproduce": "1. ...\n2. ...",
  "currentBehavior": "...",
  "expectedBehavior": "...",
  "priority": "high",
  "area": "public",
  "column": { "id": 3, "name": "En cours", "position": 3, "color": "#ffc107", "archived": false },
  "installation": { ... },
  "createdByUser": { ... },
  "comments": [...],
  "attachments": [...]
}
```

#### Bug-specific separate fields rules

- The 3 fields (`stepsToReproduce`, `currentBehavior`, `expectedBehavior`) are **always present** in API responses (null if not set).
- They are **independent from `description`** — both coexist.
- `description` is **optional** for all ticket types (not required).
- Sent to the API in **snake_case**: `steps_to_reproduce`, `current_behavior`, `expected_behavior`.
- Received from the API in **camelCase**: `stepsToReproduce`, `currentBehavior`, `expectedBehavior`.
- Only sent if non-empty (absent key = null accepted by API).

#### POST /tickets (create) — accessible to all authenticated users

```json
{
  "type": "bug",
  "title": "...",
  "description": "optional",
  "steps_to_reproduce": "optional",
  "current_behavior": "optional",
  "expected_behavior": "optional",
  "priority": "high",
  "area": "public"
}
```

#### PATCH /tickets/{id} (update) — GLOBAL_ADMIN only

All POST fields plus: `column_id`, `installation_id`, `category`, `financing`, `requested_by`, `due_date`, `started_at`.

### Frontend conventions

- **Vanilla JS (ES2020)** in `boffice-ticket-show.js` and `layout.html.twig` — **no jQuery** in these files.
- **jQuery** still used in `boffice-api.js` (shared helpers, rich ticket modal, installation toggles).
- All form logic (type toggle, CKEditor lifecycle, submit) is in `layout.html.twig`.
- CKEditor is **destroyed on modal close** and **recreated on open** with `instanceReady` event to avoid stale data.
- `resetFormFields()` explicitly clears the 3 bug textareas (JS-set values bypass `form.reset()`).

### Cache TTLs (BofficeApiClient)

| Endpoint | TTL |
|----------|-----|
| `/admin/users` | 300s |
| `/admin/installations` | 600s |
| `/kanban/columns` | 30s |
| `/tickets/{id}` (individual) | 60s (CACHE_TTL_TICKET) |
| `/tickets` (list with filters) | **not cached** |
| Sub-resources (`/tickets/{id}/comments`, etc.) | **not cached** |

Write operations (POST, PATCH, PUT, DELETE) invalidate related cache keys automatically.

### Access control

- All Boffice routes: `ROLE_ADMIN` minimum.
- Create ticket: all `ROLE_ADMIN` users.
- Update/delete/archive ticket, upload/delete attachment: `GLOBAL_ADMIN` (checked via API user list).
- `GLOBAL_ADMIN` is determined by matching the current Kohinos user email against the Boffice API `/admin/users` endpoint.

---

## Quick Start for AI Agents

### Comportement attendu de l'IA — Questions & Choix techniques

**AVANT toute implémentation**, l'IA doit poser les questions nécessaires pour bien comprendre la tâche et proposer des choix techniques clairs. Ne jamais présupposer les intentions du développeur.

#### Quand poser des questions

- **Ambiguïté fonctionnelle** : Si la demande peut être interprétée de plusieurs façons, demander laquelle est la bonne.
- **Choix d'architecture** : Si plusieurs approches sont valides (ex. : service vs controller, event vs direct call), présenter les options avec leurs avantages/inconvénients et demander.
- **Impact sur l'existant** : Si le changement peut affecter d'autres fonctionnalités, le signaler et demander confirmation.
- **Scope incertain** : Si on ne sait pas si la demande concerne le frontend, le backend, ou les deux.
- **Migration nécessaire** : Si la tâche implique une migration de base de données, demander si elle doit être incluse.

#### Format des questions

```
Avant de commencer, j'ai besoin de clarifier :

1. **[Sujet]** : [Question courte]
   - Option A : [Description courte] — avantage : X, inconvénient : Y
   - Option B : [Description courte] — avantage : X, inconvénient : Y

2. **[Sujet]** : [Question courte]
```

#### Ce que l'IA NE doit PAS faire

- ❌ Commencer à coder sans clarifier une ambiguïté importante
- ❌ Faire des choix techniques sans les signaler (ex. silencieusement choisir un pattern)
- ❌ Assumer que "la solution la plus simple" est toujours la bonne
- ❌ Ignorer des cas limites sans les mentionner
- ❌ Mettre à jour CHANGELOG sans demander si les entrées sont correctes pour les deux audiences

#### Ce que l'IA DOIT faire

- ✅ Lire le code existant avant de proposer une implémentation
- ✅ Proposer 2-3 approches techniques quand c'est pertinent
- ✅ Signaler les impacts potentiels sur d'autres parties du système
- ✅ Confirmer la compréhension de la tâche avant de commencer
- ✅ Mettre à jour CHANGELOG.md ET CHANGELOG_USER.md après chaque implémentation

---

### Before Making Changes

1. **Read relevant documentation**:
   - Check [SPECS.md](SPECS.md) for business rules
   - Check [CLAUDE.md](CLAUDE.md) for development rules
   - Check feature-specific docs in `docs/`

2. **Understand the domain**:
   - Read entity classes to understand relationships
   - Check repository methods for existing queries
   - Review service layer for similar operations

3. **Check tests**:
   - Look for existing tests covering similar functionality
   - Use `TestDataProvider` for creating test entities
   - Follow naming conventions

### When Writing Code

1. **Follow PSR-12**: Use PHP CS Fixer before committing
2. **Type everything**: Strict types, parameter types, return types
3. **Use services**: Never put business logic in controllers
4. **Named queries**: Repository methods with descriptive names
5. **Test first**: Write tests before implementation when possible

### When Writing Tests

1. **Use TestDataProvider**: Centralized test fixtures
2. **Mock infrastructure**: EntityManager, Mailer, external APIs
3. **Use real domain objects**: Don't mock entities
4. **Follow naming**: `test{Action}{ExpectedResult}[Context]`
5. **SSA transactions**: Must set reference starting with "Solidoume" and `solidoume_parameter_id`

### Before Committing

```bash
# 1. Update changelogs (ALWAYS)
# → CHANGELOG.md    : entrée technique (classes, migrations, tests)
# → CHANGELOG_USER.md : entrée fonctionnelle si visible par les utilisateurs

# 2. Run tests
vendor/bin/phpunit

# 3. Fix code style
vendor/bin/php-cs-fixer fix

# 4. Run static analysis
vendor/bin/phpstan analyze

# 5. Run all quality checks
vendor/bin/grumphp run
```

---

## Contact & Support

For questions or issues:
1. Check [DOCUMENTATION.md](DOCUMENTATION.md) for comprehensive doc index
2. Review relevant feature docs in `docs/`
3. Check tests for usage examples
4. Consult business rules in [SPECS.md](SPECS.md)

---

**End of AGENTS.md**
