npm package: @fickou/quasar-workflow
Description: Workflow components and composables for Quasar v2 / Vue 3
Author: fickou
Latest version: 1.0.2

# @fickou/quasar-workflow

Composants et composables Vue 3 pour intégrer le moteur de workflow dans une application **Quasar v2**.

Dépend uniquement de l'API REST exposée par `adonis-workflow`. Aucune dépendance directe sur le backend.

---

## Table des matières

1. [Installation](#1-installation)
2. [Enregistrement du plugin](#2-enregistrement-du-plugin)
3. [Usage de base — formulaire avec workflow](#3-usage-de-base--formulaire-avec-workflow)
4. [Directive `v-workflow-field`](#4-directive-v-workflow-field)
5. [WorkflowActionBar — boutons d'action](#5-workflowactionbar--boutons-daction)
6. [WorkflowStepper — visualisation](#6-workflowstepper--visualisation)
7. [WorkflowHistory — journal](#7-workflowhistory--journal)
8. [useWorkflow — composable principal](#8-useworkflow--composable-principal)
9. [useWorkflowWebSocket — temps réel](#9-useworkflowwebsocket--temps-réel)
10. [useWorkflowFieldControl — contrôle des champs](#10-useworkflowfieldcontrol--contrôle-des-champs)
11. [Composants de suivi et monitoring](#11-composants-de-suivi-et-monitoring)
12. [Composants d'administration](#12-composants-dadministration)
13. [useWorkflowDelegation — délégations](#13-useworkflowdelegation--délégations)
14. [useWorkflowStats — statistiques](#14-useworkflowstats--statistiques)
15. [Référence des composables](#15-référence-des-composables)
16. [Tableau formMode / canDelete](#16-tableau-formmode--candelete)

---

## 1. Installation

Ce package s'installe par **copie directe** dans le projet hôte (pas de registry npm).

```bash
# Via npm (recommandé)
npm install @fickou/quasar-workflow

# Ou via le chemin local (monorepo)
npm install file:packages/quasar-workflow
```

Vérifier que les peer dependencies sont présentes :

```json
"vue": "^3.0.0",
"quasar": "^2.0.0",
"axios": "^1.0.0"
```

---

## 2. Enregistrement du plugin

Créer un fichier boot dédié dans votre projet Quasar :

```typescript
// src/boot/workflow.ts
import { boot } from 'quasar/wrappers'
import { WorkflowPlugin } from '@fickou/quasar-workflow'

export default boot(({ app }) => {
  app.use(WorkflowPlugin, {
    // URL de base de l'API workflow (le préfixe doit correspondre à routePrefix dans adonis-workflow)
    apiBase: '/api/workflow',

    // URL du serveur WebSocket AdonisJS (optionnel — uniquement si @adonisjs/websocket est installé)
    wsUrl: 'ws://localhost:3333',
  })
})
```

Déclarer le boot dans `quasar.config.ts` :

```typescript
// quasar.config.ts
boot: [
  'axios',
  'workflow',  // ← ajouter
],
```

Le plugin enregistre automatiquement **tous les composants** (`WorkflowFormGuard`, `WorkflowActionBar`, etc.) et la **directive `v-workflow-field`** de façon globale. Aucun import supplémentaire n'est nécessaire dans vos pages.

---

## 3. Usage de base — formulaire avec workflow

Le pattern standard consiste à envelopper votre formulaire dans `WorkflowFormGuard`, qui gère automatiquement le mode du formulaire selon l'état du workflow.

```vue
<!-- src/pages/InvoicePage.vue -->
<template>
  <q-page padding>
    <WorkflowFormGuard
      entity-type="invoice"
      :entity-id="invoiceId"
      :current-user-id="currentUser.id"
      @action-executed="onActionExecuted"
    >
      <template #default="{ formMode, canEdit, canDelete, canSubmit, canResubmit, hookRejectionReason }">

        <!-- Alerte si une action a été bloquée par un hook métier -->
        <q-banner v-if="hookRejectionReason" type="warning" class="q-mb-md">
          {{ hookRejectionReason }}
        </q-banner>

        <!-- Champs du formulaire — v-workflow-field gère readonly/disable automatiquement -->
        <q-input
          v-workflow-field="'amount'"
          v-model="invoice.amount"
          label="Montant"
          type="number"
        />

        <q-input
          v-workflow-field="'description'"
          v-model="invoice.description"
          label="Description"
          type="textarea"
        />

        <!-- Boutons selon le mode -->
        <div class="row q-gutter-sm q-mt-md">
          <q-btn
            v-if="formMode === 'create'"
            label="Créer la facture"
            color="primary"
            @click="createInvoice"
          />
          <q-btn
            v-if="canEdit"
            label="Enregistrer"
            color="primary"
            @click="saveInvoice"
          />
          <q-btn
            v-if="canDelete"
            label="Supprimer"
            color="negative"
            @click="deleteInvoice"
          />
        </div>

        <!-- Barre d'actions workflow (boutons approve, reject, etc.) -->
        <WorkflowActionBar
          entity-type="invoice"
          :entity-id="invoiceId"
          class="q-mt-md"
        />

        <!-- Visualisation de l'avancement -->
        <WorkflowStepper
          entity-type="invoice"
          :entity-id="invoiceId"
          class="q-mt-lg"
        />

      </template>
    </WorkflowFormGuard>
  </q-page>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import { useAuthStore } from 'src/stores/auth'

const props = defineProps<{ invoiceId: string | null }>()
const authStore = useAuthStore()
const currentUser = computed(() => authStore.user)
const invoice = ref({ amount: 0, description: '' })

const onActionExecuted = (action: string) => {
  // Rafraîchir les données après une action workflow
  console.log('Action exécutée :', action)
}

async function createInvoice() { /* ... */ }
async function saveInvoice() { /* ... */ }
async function deleteInvoice() { /* ... */ }
</script>
```

### Props de `WorkflowFormGuard`

| Prop | Type | Requis | Description |
|---|---|---|---|
| `entity-type` | `string` | ✅ | Type d'entité (`invoice`, `purchase_order`…) |
| `entity-id` | `string \| null` | ✅ | ID du document. `null` → mode création |
| `current-user-id` | `string` | ✅ | ID de l'utilisateur courant |

### Slot `#default`

| Variable | Type | Description |
|---|---|---|
| `formMode` | `FormMode` | Mode courant du formulaire |
| `canEdit` | `boolean` | L'utilisateur peut modifier les champs |
| `canDelete` | `boolean` | L'utilisateur peut supprimer le document |
| `canSubmit` | `boolean` | Le workflow peut être démarré |
| `canResubmit` | `boolean` | Le document peut être resoumis après correction |
| `hookRejectionReason` | `string \| null` | Message de refus d'un ActionHook |
| `instance` | `WorkflowInstanceDTO \| null` | Instance de workflow courante |

---

## 4. Directive `v-workflow-field`

La directive `v-workflow-field` applique automatiquement `readonly` ou `disable` sur un champ selon les droits de l'étape courante.

```vue
<!-- readonly si l'étape n'autorise pas la modification du champ -->
<q-input v-workflow-field="'amount'" v-model="form.amount" />
<q-select v-workflow-field="'category'" v-model="form.category" :options="categories" />
```

**Prérequis** : la directive doit être utilisée à l'intérieur d'un `WorkflowFormGuard`. Celui-ci expose via `provide()` la fonction `workflowFieldState` consommée par la directive.

Les champs éditables sont définis dans la propriété `editable_fields` de chaque étape de la définition workflow.

---

## 5. WorkflowActionBar — boutons d'action

Affiche les boutons d'action disponibles pour l'étape courante (approve, reject, transfer, etc.).

```vue
<WorkflowActionBar
  entity-type="invoice"
  :entity-id="invoiceId"
  @action-executed="onActionExecuted"
  @hook-rejected="onHookRejected"
/>
```

### Props

| Prop | Type | Description |
|---|---|---|
| `entity-type` | `string` | Type d'entité |
| `entity-id` | `string \| null` | ID du document |

### Événements émis

| Événement | Payload | Description |
|---|---|---|
| `action-executed` | `{ action: string }` | Après une action réussie |
| `hook-rejected` | `{ reason: string }` | Quand un ActionHook bloque |

### Actions affichées automatiquement selon l'étape

Les actions sont filtrées selon la configuration `WorkflowStepAction` de l'étape courante. La `WorkflowActionBar` gère automatiquement :

- Les **dialogs de saisie de raison** pour les actions qui la requièrent
- Le **champ "Transférer à"** pour l'action `transfer`
- Le **verrouillage optimiste** (passage automatique du `version` actuel)
- Le **bouton Soumettre** (démarrage du workflow) quand pas d'instance
- Le **bouton Re-soumettre** en mode correction

---

## 6. WorkflowStepper — visualisation

Affiche la progression du workflow avec le statut de chaque étape.

```vue
<WorkflowStepper
  entity-type="invoice"
  :entity-id="invoiceId"
/>
```

Gère automatiquement les trois topologies :
- **Séquentielle** : étapes affichées en ligne
- **Parallèle** : étapes `fork`/`join` affichées côte à côte
- **Conditionnelle** : étapes skippées affichées en grisé

---

## 7. WorkflowHistory — journal

Affiche l'historique complet des actions sous forme de timeline.

```vue
<WorkflowHistory
  entity-type="invoice"
  :entity-id="invoiceId"
/>
```

Chaque entrée affiche : action, acteur, date, raison (si présente).

---

## 8. useWorkflow — composable principal

Pour un contrôle fin, utiliser le composable directement :

```typescript
import { useWorkflow } from '@fickou/quasar-workflow'
import { ref } from 'vue'

const entityId = ref<string | null>('inv-123')

const {
  instance,       // Ref<WorkflowInstanceDTO | null>
  definition,     // Ref<WorkflowDefinitionDTO | null>
  loading,        // Ref<boolean>
  error,          // Ref<string | null>
  hookRejectionReason,  // Ref<string | null>

  fetchInstanceAndDefinition,  // () => Promise<void>
  submitWorkflow,    // () => Promise<void> — démarre le workflow
  resubmit,         // () => Promise<void> — resoumet après correction
  executeAction,    // (action, payload) => Promise<void>
} = useWorkflow('invoice', entityId)

// Charger au montage
await fetchInstanceAndDefinition()

// Exécuter une action
await executeAction('approve', {
  version: instance.value!.steps[0].version,
  reason: 'Conforme au budget',
})
```

---

## 9. useWorkflowWebSocket — temps réel

Le WebSocket permet de recevoir les mises à jour en temps réel sans rafraîchir la page.

```typescript
import { useWorkflowWebSocket } from '@fickou/quasar-workflow'
import { ref } from 'vue'

const instanceId = ref<string | null>('inst-456')

const { isConnected } = useWorkflowWebSocket(instanceId, {
  onStepChanged: (data) => {
    console.log('Étape changée :', data)
    // Rafraîchir l'état de l'instance
    fetchInstanceAndDefinition()
  },
  onActionExecuted: (data) => {
    console.log('Action exécutée par un autre utilisateur :', data)
  },
  onConflict: (data) => {
    // Un autre utilisateur a agi en même temps
    Notify.create({ type: 'warning', message: 'L\'état a changé. Rechargement...' })
    fetchInstanceAndDefinition()
  },
})
```

La reconnexion est gérée automatiquement avec un **backoff exponentiel** (base 1s, ×2 à chaque tentative, max 30s).

---

## 10. useWorkflowFieldControl — contrôle des champs

Pour contrôler l'état des champs sans passer par la directive :

```typescript
import { useWorkflowFieldControl } from '@fickou/quasar-workflow'
import { ref } from 'vue'

const entityId = ref<string | null>('inv-123')

const {
  formMode,    // ComputedRef<FormMode>
  canEdit,     // ComputedRef<boolean>
  canDelete,   // ComputedRef<boolean>
  isLoading,   // Ref<boolean>
  fieldState,  // (fieldName: string) => FieldState ('editable' | 'readonly' | 'disabled')
} = useWorkflowFieldControl('invoice', entityId, {
  currentUserId: 'user-123',
  creatorId: 'user-123',
})

// Utilisation manuelle sans directive
const amountState = computed(() => fieldState('amount'))
```

---

## 11. Composants de suivi et monitoring

### WorkflowDashboard — documents en attente d'action

Affiche la liste de tous les documents sur lesquels l'utilisateur courant doit agir.

```vue
<WorkflowDashboard :user-id="currentUser.id" />
```

### WorkflowTracking — suivi créateur

Permet au créateur de suivre l'avancement de ses propres documents.

```vue
<WorkflowTracking entity-type="invoice" :entity-id="invoiceId" />
```

### WorkflowAdminMonitor — monitoring global (workflow_manager)

Tableau de bord global avec filtres par statut, entity_type et période.

```vue
<WorkflowAdminMonitor />
```

---

## 12. Composants d'administration

### WorkflowAdminBuilder — constructeur de workflow (workflow_admin)

Interface complète pour créer et modifier les définitions de workflow (étapes + transitions).

```vue
<!-- Page d'administration -->
<WorkflowAdminBuilder entity-type="invoice" />
```

Inclut deux onglets :
- **Étapes** : ajouter/modifier/supprimer les étapes, configurer deadline, validateurs, actions autorisées
- **Transitions** : définir les conditions de passage entre étapes via `RuleBuilderWidget`

### WorkflowTransitionEditor — éditeur de transitions

```vue
<WorkflowTransitionEditor :definition-id="defId" />
```

### RuleBuilderWidget — éditeur visuel de règles

Interface visuelle pour créer des conditions AND/OR imbriquées. Supporte les 15 opérateurs du `RuleEngineService`.

```vue
<RuleBuilderWidget
  v-model="ruleSet"
  :available-fields="['amount', 'department', 'status']"
/>
```

---

## 13. useWorkflowDelegation — délégations

```typescript
import { useWorkflowDelegation } from '@fickou/quasar-workflow'

const {
  myDelegations,       // Ref<WorkflowDelegation[]>
  receivedDelegations, // Ref<WorkflowDelegation[]>
  loading,
  fetchMyDelegations,
  fetchReceivedDelegations,
  createDelegation,
  revokeDelegation,
} = useWorkflowDelegation()

// Créer une délégation
await createDelegation({
  delegateId: 'user-456',
  scope: 'workflow',
  workflowDefinitionId: 'def-789',
  startDate: new Date().toISOString(),
  endDate: new Date(Date.now() + 7 * 86400000).toISOString(),
  type: 'delegation',
})
```

Ou utiliser directement le composant :

```vue
<WorkflowDelegationManager :user-id="currentUser.id" />
```

---

## 14. useWorkflowStats — statistiques

Disponible uniquement pour les utilisateurs `workflow_manager` et `workflow_admin`.

```typescript
import { useWorkflowStats } from '@fickou/quasar-workflow'

const {
  delays,         // Ref<DelayStats[]>
  rejectionRates, // Ref<RejectionRateStats[]>
  volumes,        // Ref<VolumeStats[]>
  loading,
  fetchDelays,
  fetchRejectionRates,
  fetchVolumes,
} = useWorkflowStats()

await fetchDelays({ entityType: 'invoice', period: '30d' })
```

Ou utiliser le composant intégré :

```vue
<WorkflowStats entity-type="invoice" />
```

---

## 15. Référence des composables

| Composable | Paramètres | Usage |
|---|---|---|
| `useWorkflow(entityType, entityId, options?)` | `entityType: string`, `entityId: Ref<string\|null>` | Composable principal — état + actions |
| `useWorkflowFieldControl(entityType, entityId, options?)` | idem + `currentUserId`, `creatorId` | formMode, canEdit, fieldState |
| `useWorkflowWebSocket(instanceId, callbacks?)` | `instanceId: Ref<string\|null>` | Connexion WS + callbacks |
| `useWorkflowAdmin()` | — | CRUD définitions/steps/transitions |
| `useWorkflowDashboard()` | — | Liste des instances en attente |
| `useWorkflowStats()` | — | Statistiques (manager+) |
| `useWorkflowDelegation()` | — | Gestion des délégations |

---

## 16. Tableau formMode / canDelete

| Contexte | `formMode` | `canEdit` | `canDelete` |
|---|---|---|---|
| `entity_id = null` | `create` | `true` | `false` |
| Pas d'instance active | `view` | `false` | `true` si `ever_started = false` |
| Instance `in_progress` | `edit` | `true` (champs editable_fields) | `false` |
| Instance `suspended` | `suspended` | `true` (champs editable_fields) | `false` |
| `correction_pending` (créateur) | `correction` | `true` | `false` |
| `completed / cancelled / rejected` | `locked` | `false` | `false` |

> **Règle absolue** : dès que `ever_started = true`, `canDelete = false` de façon **permanente**, même si l'instance est terminée. Toujours vérifier côté backend avant toute suppression.

---

## Développement et Exemple

Le package contient un exemple complet d'intégration dans `examples/quasar-example`.

### 1. Installation de l'exemple

Depuis la racine du package `packages/quasar-workflow` :

```bash
npm run example:install
```

### 2. Lancer l'exemple en mode développement

Depuis la racine du package `packages/quasar-workflow` :

```bash
npm run example:dev
```

L'application sera accessible sur `http://localhost:9000`.

> **Note** : L'exemple Quasar nécessite que le backend `adonis-example` soit lancé pour fonctionner correctement (API et WebSockets).