From 4034c3990525636d2215f29af89b5a5ae2d38390 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 27 Jun 2026 13:30:28 +0100 Subject: [PATCH] Front matters --- Directory.Packages.props | 1 + doc/Architecture.md | 178 +++++++++++++++++- .../Models/Billing/FrontmatterOffreResult.cs | 8 + .../Models/Billing/Service/ChatBilling.cs | 5 - .../Services/FrontmatterParser.cs | 32 ++++ src/Yavsc.Server/Yavsc.Server.csproj | 3 +- 6 files changed, 215 insertions(+), 12 deletions(-) create mode 100644 src/Yavsc.Server/Models/Billing/FrontmatterOffreResult.cs delete mode 100644 src/Yavsc.Server/Models/Billing/Service/ChatBilling.cs create mode 100644 src/Yavsc.Server/Services/FrontmatterParser.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index d5ce67a3..d3664121 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -27,5 +27,6 @@ + \ No newline at end of file diff --git a/doc/Architecture.md b/doc/Architecture.md index 52e05254..0ee1b784 100644 --- a/doc/Architecture.md +++ b/doc/Architecture.md @@ -113,13 +113,15 @@ Rendu CSS/HTML — vert si `EstLibre`, orange sinon. - **ORM** : Entity Framework Core (migrations générées) - **Base de données** : PostgreSQL (provider Npgsql) - **Frontend** : Razor views +- **Parsing frontmatter** : YamlDotNet -## Droits de yavsc +--- + +## Droits de Yavsc ### Administration -Le groupe des administrateurs prend la charge de : - +Le groupe des administrateurs prend la charge de : - la Gestion des licences - la Gestion des groupes d'utilisateurs (les modérateurs, en particulier) - la Gestion des projets @@ -127,13 +129,176 @@ Le groupe des administrateurs prend la charge de : ### Gestion des utilisateurs et des groupes -En supposant que les certificats de letsencrypt sont au groupe `www-data` , +En supposant que les certificats de letsencrypt sont au groupe `www-data`, on peut créer l'utilisateur `yavsc` avec les droits suivants : -¨¨¨¨bash +```bash sudo addgroup yavsc --system sudo adduser --ingroup yavsc --add-extra-groups www-data \ --disabled-password --system yavsc +``` + +--- + +## Domaines d'activité + +### Hiérarchie + +Les activités sont organisées en arbre. Chaque activité peut avoir +une activité parente d'ordre plus général : + +``` +Droit ← racine, ancêtre commun +├── Musique +│ ├── Jazz +│ ├── Classique +│ └── ... +├── Graphisme +├── BTP +└── ... +``` + +Le domaine **Droit** est positionné à la racine — *nul n'est censé +ignorer la loi*. Sa position structurelle rend ses dictionnaires +naturellement disponibles dans tous les projets, sans attribut spécial. + +### Modèle `DomaineActivite` + +``` +DomaineActivite + - Id, Nom + - ParentId (FK → DomaineActivite, nullable) +``` + +Pas de booléen `EstTransversal` — la transversalité est une conséquence +de la position dans l'arbre, pas un attribut explicite. + +--- + +## Dictionnaires métier + +### Principe + +Les dictionnaires sont des **modèles publics** de termes métier, +maintenus par les modérateurs et alimentés par les fournisseurs. +Au moment de créer un projet, le dictionnaire est **copié** — +c'est un instantané, pas une référence vivante. + +### Règle de résolution + +> Un projet hérite des dictionnaires de son activité **et de tous +> ses ancêtres** jusqu'à la racine. + +Ainsi, les termes juridiques sont toujours disponibles, quel que +soit le domaine du projet. + +### Modèle + +``` +DictionnaireMetier + - Id, Nom + - DomaineActiviteId (FK) + - Langue ← attribut du dictionnaire entier + +TermeMetier + - Id + - DictionnaireMetierId (FK) + - Mot + - Definition + - StatutValidation ← Proposé | Validé | Rejeté + - ProposeParId (FK → ApplicationUser) + - ValidéParId (FK → ApplicationUser, nullable) +``` + +### Cycle de vie d'un terme + +1. Un **fournisseur** propose un terme dans son domaine et sa langue +2. Un **modérateur** valide → le terme intègre le dictionnaire public +3. À la création d'un projet, le dictionnaire est **associé par copie** + +--- + +## Offre fournisseur et modèle canonique de demande + +### Principe + +Un fournisseur décrit son offre en **Markdown**, avec un en-tête +structuré délimité par `---` (frontmatter YAML standard) qui porte +les métadonnées de formulaire et de devis. Le corps est libre. + +```markdown +--- +formulaire: Prestation Musicale +devis: Arrangement Orchestral +--- + +Je propose des arrangements pour orchestre de chambre, +livraison sous 3 semaines, formats MusicXML et PDF... +``` + +### `ClasseFormulaire` et `ClasseDevis` + +Ces deux référentiels sont **définis côté serveur par les modérateurs**, +pour servir de modèles de formulaires et de devis. Les fournisseurs +s'y conforment — ils ne peuvent pas en créer de nouveaux. + +### Modèle canonique nom/valeur + +``` +ClasseFormulaire + - Id, Nom + - DomaineActiviteId (FK) + └── ChampDemande + - Nom (ex: "tempo", "tonalité", "durée") + - TypeValeur (texte | entier | décimal | booléen | enum) + - Obligatoire (bool) + - ValeurDefaut + +ClasseDevis + - Id, Nom + - DomaineActiviteId (FK) + +OffreFournisseur + - Id + - FournisseurId (FK → ApplicationUser) + - DomaineActiviteId (FK) + - ContenuMarkdown ← corps libre de l'offre + - ClasseFormulaireId (FK) ← extrait du frontmatter à la sauvegarde + - ClasseDevisId (FK) ← extrait du frontmatter à la sauvegarde +``` + +### Demande client + +À partir de l'offre, la demande instancie les champs du formulaire : + +``` +Demande + - Id + - OffreFournisseurId (FK) + - ClientId (FK → ApplicationUser) + └── ValeurChampDemande + - ChampDemandeId (FK) + - Valeur (string — sérialisé selon TypeValeur) +``` + +### Règle d'héritage + +Les `ClasseFormulaire` et `ClasseDevis` disponibles pour une activité +incluent ceux de ses **ancêtres** dans l'arbre — cohérent avec la +règle de résolution des dictionnaires. + +### Parsing du frontmatter avec YamlDotNet + +```csharp +var parts = markdown.TrimStart() + .Substring(3) + .Split(new[] { "\n---" }, 2, + StringSplitOptions.None); +var yamlBlock = parts[0].Trim(); +var corps = parts[1].Trim(); +var meta = deserializer.Deserialize(yamlBlock); +``` + --- ## À documenter ensuite @@ -141,4 +306,5 @@ sudo adduser --ingroup yavsc --add-extra-groups www-data \ - Modèle `ProjetMusical` - Gestion des fichiers (audio, partition, MIDI) - Workflow de validation de licence par l'administration - +- Workflow de modération des termes métier +- Workflow complet de parsing et validation du frontmatter à la sauvegarde diff --git a/src/Yavsc.Server/Models/Billing/FrontmatterOffreResult.cs b/src/Yavsc.Server/Models/Billing/FrontmatterOffreResult.cs new file mode 100644 index 00000000..e7f42698 --- /dev/null +++ b/src/Yavsc.Server/Models/Billing/FrontmatterOffreResult.cs @@ -0,0 +1,8 @@ +namespace Yavsc.Server.Models.Billing; + +public class FrontmatterOffreResult +{ + public string Formulaire { get; set; } + public string Devis { get; set; } + public string Corps { get; set; } // le Markdown sans l'en-tête +} diff --git a/src/Yavsc.Server/Models/Billing/Service/ChatBilling.cs b/src/Yavsc.Server/Models/Billing/Service/ChatBilling.cs deleted file mode 100644 index 9a305c44..00000000 --- a/src/Yavsc.Server/Models/Billing/Service/ChatBilling.cs +++ /dev/null @@ -1,5 +0,0 @@ - - -class ChatBilling {  - -} diff --git a/src/Yavsc.Server/Services/FrontmatterParser.cs b/src/Yavsc.Server/Services/FrontmatterParser.cs new file mode 100644 index 00000000..6514c508 --- /dev/null +++ b/src/Yavsc.Server/Services/FrontmatterParser.cs @@ -0,0 +1,32 @@ +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; +using Yavsc.Server.Models.Billing; + +namespace Yavsc.Server.Services; + +public static class FrontmatterParser +{ + public const string YamlSeparator = "---"; + + private static readonly IDeserializer _deserializer = + new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + + public static FrontmatterOffreResult Parse(string markdown) + { + // Le contenu doit commencer par ---- + if (!markdown.TrimStart().StartsWith(YamlSeparator)) + return new FrontmatterOffreResult { Corps = markdown }; + var parts = markdown.TrimStart()[3..].Split(new[] { "\n"+YamlSeparator }, 2, + StringSplitOptions.None); + if (parts.Length < 2) + return new FrontmatterOffreResult { Corps = markdown }; + var yamlBlock = parts[0].Trim(); + var corps = parts[1].Trim(); + var meta = _deserializer.Deserialize(yamlBlock); + meta.Corps = corps; + return meta; + } +} diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index ceaf4322..a1e96ab2 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -31,8 +31,9 @@ + - \ No newline at end of file +