Commit 9e57ae0f authored by Anthony Gautreau's avatar Anthony Gautreau
Browse files

Version initiale de unicaen/workflow-bundle et @unicaen/workflow

parents
Loading
Loading
Loading
Loading

.gitignore

0 → 100644
+14 −0
Changes for .gitignore: 14 added lines, 0 removed lines.
Original line number Diff line number Diff line
.idea
.vscode
.env
vendor
api/vendor
api/composer.lock
frontend/node_modules
frontend/dist
###> friendsofphp/php-cs-fixer ###
/.php-cs-fixer.php
/.php-cs-fixer.cache
api/.php-cs-fixer.php
api/.php-cs-fixer.cache
###< friendsofphp/php-cs-fixer ###

.gitlab-ci.yml

0 → 100644
+45 −0
Changes for .gitlab-ci.yml: 45 added lines, 0 removed lines.
Original line number Diff line number Diff line
image: node:22.14

stages:
  - build
  - publish

variables:
  JS_DIR: frontend
  # La variable de proxy héritée du groupe n'a pas de schéma, npm la refuse : on lui donne la sienne.
  npm_config_proxy: http://proxy.unicaen.fr:3128
  npm_config_https_proxy: http://proxy.unicaen.fr:3128
  npm_config_noproxy: unicaen.fr

build_js:
  stage: build
  script:
    - cd $JS_DIR
    - npm ci
    - npm run build
  artifacts:
    paths:
      - frontend/dist
  rules:
    - exists:
        - frontend/package.json

# Publier sur le packagist
publish_php:
  stage: publish
  image: alpine/curl
  rules:
    - if: '$CI_COMMIT_TAG'
  script:
    - export NO_PROXY=unicaen.fr ; curl 'https://packagist.unicaen.fr/build?name=unicaen/workflow-bundle'

# Publier sur GitLab Registry
publish_js:
  stage: publish
  rules:
    - if: '$CI_COMMIT_TAG'
  script:
    - cd $JS_DIR
    - echo "@unicaen:registry=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/" > .npmrc
    - echo "${CI_API_V4_URL#https:}/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}" >> .npmrc
    - npm publish --access public

CHANGELOG.md

0 → 100644
+11 −0
Changes for CHANGELOG.md: 11 added lines, 0 removed lines.
Original line number Diff line number Diff line
## 1.0.0

- Refonte de bcedric/symfony-workflow-dynamic-bundle sous `unicaen/workflow-bundle`, sans reprise de ses tables : circuits (`workflow` ou `state_machine`), places, transitions, tâches, journal, tables `unicaen_workflow_*`
- Tâches de place (formulaire, dépôt de fichiers, attestation, types ajoutés par `TaskTypeInterface`) qui bloquent la sortie de la place, `requireComment` et `resetTasks` sur les transitions, `performer` des tâches
- Révisions des tâches terminées, historique du sujet fusionné avec les transitions, fichiers d'une révision conservés
- Propriétés du sujet par `#[WorkflowProperty]` ou l'option `properties`, pour les champs liés et les mails de transition, envoyés par `TransitionMailerInterface` (Symfony Mailer par défaut)
- Circuits en cours protégés : place renommée reportée dans le marking des sujets, suppression d'une place occupée et changement de type ou de classe refusés (409)
- Expressions `guard` et `performer` contrôlées à l'enregistrement, limitées aux accesseurs, jamais renvoyées aux clients
- Intégration API Platform facultative : ressources des circuits et opérations du workflow ajoutées à chaque sujet, droits par `WorkflowVoter` (`WORKFLOW_MANAGE`, `WORKFLOW_VIEW`, `WORKFLOW_ACT`, `WORKFLOW_OWNER`)
- Paquet npm `@unicaen/workflow` : éditeur de circuits (`WorkflowEditor`, panneau d'édition des étapes, transitions et tâches), composants de suivi, store, textes fr et en
- Symfony 7.4 et 8

README.md

0 → 100644
+110 −0
Changes for README.md: 110 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Unicaen Workflow

Circuits [Symfony Workflow](https://symfony.com/doc/current/workflow.html) stockés en base, donc modifiables sans
redéploiement, avec ce qu'un circuit administratif demande autour : tâches à réaliser sur une étape (formulaire,
dépôt de fichiers, attestation), historique des transitions et des saisies, mails de transition. Le moteur reste le
composant Symfony : guards, événements, marking store et dumpers fonctionnent comme pour `framework.workflows`.

Reprise et refonte de [BCedric/SymfonyWorkflowDynamicBundle](https://github.com/BCedric/SymfonyWorkflowDynamicBundle)

## En deux minutes

```php
#[ORM\Entity]
#[ApiResource(operations: [new Get(), new Post()])]
class Dossier implements WorkflowSubjectInterface
{
    use WorkflowSubjectTrait;            // colonnes workflowCode et marking

    #[ORM\Column]
    #[WorkflowProperty(writable: true)]  // utilisable dans les formulaires et les mails du circuit
    private ?string $nom = null;
}
```

Un dossier persisté avec un `workflowCode` est posé sur la place initiale de son circuit. Avec API Platform, ses
opérations de workflow sont ajoutées d'office : `GET /dossiers/{id}/workflow` (état), `POST .../transitions`,
`POST .../tasks/{task}`, `GET .../files/{file}`, `GET .../history`. Les circuits se gèrent par `/workflow_definitions`,
`/workflow_places`, `/workflow_transitions` et `/workflow_tasks`.

## Installation

```bash
composer require unicaen/workflow-bundle
npm install @unicaen/workflow
```

Modèle, configuration, sécurité et API : **[api/README.md](api/README.md)**.

### Front

```js
import { WorkflowPlugin, workflowMessages } from '@unicaen/workflow'

app.use(WorkflowPlugin, {
  axios: api,                      // instance axios de l'application, base /api
  loadRoles: () => fetchRoles(),   // facultatif, [{code, libelle}] proposés dans les conditions
})

// Textes fr et en des composants, à fusionner dans l'i18n de l'application.
i18n.global.mergeLocaleMessage('fr', workflowMessages.fr)
```

`axios` est obligatoire : c'est l'instance de l'application, avec son authentification. L'API doit être servie sous
`/api`, préfixe des IRI que le paquet envoie. Les plugins Quasar `Notify` et `Dialog` doivent être installés dans
l'application : le paquet utilise le Quasar de l'application, il n'en embarque pas de copie. Sans `Dialog`, les
confirmations ne s'ouvrent pas et les suppressions ne partent pas.

```vue
<WorkflowEditor
  :subject-classes="[{ value: 'App\\Entity\\Dossier', label: 'Dossier' }]"
  :can-create="isAuthorized('workflow_creer')"
  :can-edit="isAuthorized('workflow_modifier')"
  :can-delete="isAuthorized('workflow_supprimer')"
  subjects-label="Dossiers"
>
  <!-- facultatif : onglet des sujets du circuit, avec SubjectWorkflow par exemple -->
  <template #subjects="{ definition }">...</template>
</WorkflowEditor>
```

`WorkflowEditor` gère les circuits : liste, schéma ou liste détaillée, et panneau d'édition des étapes, transitions et
tâches. Autres composants : `SubjectWorkflow` (suivi complet d'un sujet), `SubjectWorkflowCard` (tâches et transitions
de qui le fait avancer), `SubjectProgress` (avancement condensé), `WorkflowDiagram`, `TaskConfigEditor`,
`TransitionMailEditor`, `TaskPanel`. Store : `useWorkflowStore`. Le paquet suppose Vue 3, Pinia, Quasar et vue-i18n
dans l'application.

Les props `can-create`, `can-edit` et `can-delete` masquent les actions de l'éditeur, l'API refuse de toute façon ce qui
n'est pas permis.

---

## Structure du dépôt

```
/api       # Bundle Symfony (paquet unicaen/workflow-bundle)
/frontend  # Éditeur de circuits et composants de suivi, Vue 3 et Quasar (paquet @unicaen/workflow)
/docs      # Exemple de référentiel des privilèges
```

## Développement

```bash
cd frontend && npm install && npm run build-watch
```

## Publication

```bash
./release-api.sh 1.0.0     # version explicite, pour la première release
./release-api.sh patch     # 1.0.0 -> 1.0.1
```

Le script aligne `api/.version` et `frontend/package.json`, commite, produit un `git subtree split --prefix=api` poussé
sur la branche `split-api` et y pose le tag de version. La branche courante reçoit un tag `m-<version>`, qui déclenche la
CI : publication sur le packagist Unicaen et sur le registre npm du projet. À lancer sur l'hôte, avec npm, working tree
propre.

## Licence

MIT
+13 −0
Changes for api/.php-cs-fixer.dist.php: 13 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

$finder = (new PhpCsFixer\Finder())
    ->in(__DIR__.'/src')
    ->exclude('vendor')
;

return (new PhpCsFixer\Config())
    ->setRules([
        '@Symfony' => true,
    ])
    ->setFinder($finder)
;
Loading