@mostajs/flotte 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,7 +3,21 @@
3
3
  Format [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) · [SemVer](https://semver.org/lang/fr/).
4
4
  Auteur : Dr Hamid MADANI <drmdh@msn.com>
5
5
 
6
- ## [0.1.0] — non publiée
6
+ ## [0.3.0] — non publiée
7
+ ### Added
8
+ - `vehicules({ statut?, type?, site?, q?, reformes? })` : la liste du parc, rangée par code ; `statut` est un code
9
+ ou une liste ; `q` est cherché dans le code, la plaque, la marque et le modèle, sans casse, espaces et tirets
10
+ indifférents ; les réformés n'y sont que sur demande. Demandé par le tableau de bord ECR-16 de Guerway, que
11
+ `@mostajs/flotte-ui` construit sans accéder aux dépôts. Essai T-FLO-17.
12
+
13
+ ## [0.2.0] — non publiée
14
+ ### Added
15
+ - `prochainsEntretiens(v, { date? })` et `prochainEntretien(v)` : pour chaque plan, l'échéance de chaque pas (km,
16
+ jours) et la date du premier atteint, rangés du plus urgent au moins urgent. Un pas en km devient une date au rythme
17
+ relevé (`estime: true`). Demandé par l'écran ECR-16 de Guerway (« Prochain entretien : date, dans N jours »).
18
+ - Règles pures `regles.prochainEntretien` et `regles.kmParJour` (rythme sur les 90 derniers jours). Essai T-FLO-16.
19
+
20
+ ## [0.1.0] — 2026-10-24
7
21
  ### Added
8
22
  - `createFlotte` : registre par types déclarés, cycle en service / immobilisé / à l'atelier / réformé
9
23
  (`@mostajs/statemachine`), échéances typées, plans d'entretien au km et à la date, interventions numérotées,
package/README.md CHANGED
@@ -5,10 +5,12 @@
5
5
  Le carnet du parc. Il répond à une question : **ce véhicule peut-il partir aujourd'hui, avec ce conducteur, et
6
6
  combien coûte-t-il ?**
7
7
 
8
- - un registre de véhicules, chacun d'un **type déclaré** par l'application (fourgon, moto, autocar…) ;
8
+ - un registre de véhicules, chacun d'un **type déclaré** par l'application (fourgon, moto, autocar…), et sa liste
9
+ filtrée par statut, type, site ou texte ;
9
10
  - un cycle : en service, immobilisé, à l'atelier, réformé ; chaque passage est tracé, avec son motif ;
10
11
  - des échéances (assurance, contrôle technique…) ; la plus récente fait foi, les anciennes restent lisibles ;
11
- - l'entretien au kilométrage **et** à la date, dû au premier des deux atteint, avec une tolérance ;
12
+ - l'entretien au kilométrage **et** à la date, dû au premier des deux atteint, avec une tolérance ; le prochain
13
+ entretien est daté, et un pas en km devient une date au rythme relevé ;
12
14
  - des relevés de compteur : un recul est refusé, sauf correction motivée ; un saut anormal est signalé ;
13
15
  - des pleins, et la consommation mesurée entre deux pleins complets ;
14
16
  - des affectations de conducteurs (identifiant opaque) ;
@@ -47,7 +49,7 @@ alertes (`notifications`), fichiers scannés (`blob-store`). `peutPartir` dit **
47
49
  ## Vérifier
48
50
 
49
51
  ```bash
50
- npm test # T-FLO-1 à 15 (@mostajs/mjs-unit)
52
+ npm test # T-FLO-1 à 16 (@mostajs/mjs-unit)
51
53
  node examples/verifier.mjs # un fourgon, de l'inscription au coût au km
52
54
  ```
53
55
 
package/llms.txt CHANGED
@@ -19,15 +19,18 @@ EXPORTS
19
19
  createMemoryRepositories() 7 dépôts : vehicules, echeances, plans, interventions, releves, pleins, affectations
20
20
  ErreurFlotte { code, statut } (400 donnée, 404 inconnu, 409 doublon/recul/close/ouverte/occupé, 422 transition)
21
21
  CODES, LIBELLES, libelle(famille, code, t?, vars?) ; STATUTS, MOTIFS ; PERMISSIONS (flotte:domaine:action)
22
- regles (pur) : etatEntretien, consommation, evaluerDepart, cout, echeanceCourante, jours, ajouterJours
22
+ regles (pur) : etatEntretien, prochainEntretien, kmParJour, consommation, evaluerDepart, cout, echeanceCourante, jours, ajouterJours
23
23
  register (7 tables ; aussi './register'), SCHEMAS, validerDeclarations
24
24
 
25
25
  API (async ; ctx = { agent })
26
26
  inscrire({ code, immatriculation, type, attributs, marque?, site?, mode? }, ctx) ; lire(id)
27
+ vehicules({ statut?, type?, site?, q?, reformes? }) (0.3.0) -> copies rangées par code ; réformés exclus sauf demande
27
28
  changerStatut(id, 'immobiliser'|'atelier'|'remettre'|'reformer', { agent, motif }) motif requis : immobiliser, reformer
28
29
  poserEcheance({ vehicule, type, debut, fin?, cout?, piece? }, ctx) fin absente = debut + periodiciteJours
29
30
  echeances(v) ; aEcheance({ dans?, date? }) -> [{ id, vehicule, type, fin, n, expiree }]
30
31
  planifierEntretien({ vehicule, libelle, tousLesKm?, tousLesJours?, toleranceKm?, toleranceJours? }, ctx) ; plans(v) ; entretiensDus({ date? })
32
+ prochainsEntretiens(v, { date? }) / prochainEntretien(v) (0.2.0) -> { plan, libelle, etat, km: { echeance, restant },
33
+ jours: { echeance, restant }, le, dans, estime } le pas en km devient une date au rythme relevé (estime: true)
31
34
  ouvrirIntervention({ vehicule, nature, plan?, description?, ouverteLe? }, ctx) corrective => a_l_atelier
32
35
  cloreIntervention(id, { km?, cout?, le? }, ctx) recale le plan ; km => relevé ; close => immuable
33
36
  relever(v, { km, le?, source?, correction?: { motif, agent } }, ctx) -> relevé + avertissements (SAUT_ANORMAL)
@@ -46,5 +49,5 @@ PIÈGES
46
49
  - L'entité de relevé s'appelle ReleveCompteur (Releve appartient à @mostajs/recette).
47
50
  - L'échéance qui fait foi est la plus récente par début ; les anciennes restent lisibles.
48
51
 
49
- ESSAIS test-scripts/unit/flotte.test.mjs — T-FLO-1 à 15 ; exemple : node examples/verifier.mjs
52
+ ESSAIS test-scripts/unit/flotte.test.mjs — T-FLO-1 à 16 ; exemple : node examples/verifier.mjs
50
53
  LICENCE AGPL-3.0-or-later · Auteur Dr Hamid MADANI <drmdh@msn.com>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/flotte",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Le carnet du parc : registre des véhicules par type déclaré, cycle d'état, échéances, entretien au kilométrage et à la date, relevés, pleins, affectations de conducteurs, coût au km, et la réponse « ce véhicule peut-il partir ? ».",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
@@ -60,8 +60,10 @@
60
60
  ],
61
61
  "verbes": [
62
62
  "inscrire",
63
+ "vehicules",
63
64
  "changerStatut",
64
65
  "peutPartir",
66
+ "prochainEntretien",
65
67
  "aEcheance",
66
68
  "entretiensDus",
67
69
  "saisirPlein",
package/src/flotte.js CHANGED
@@ -102,6 +102,16 @@ export function createFlotte({
102
102
  return r;
103
103
  }
104
104
 
105
+ /** Les prochains entretiens d'un véhicule, du plus urgent au moins urgent (sans date : en dernier). */
106
+ async function prochainsEntretiens(vehicule, { date } = {}) {
107
+ await lire(vehicule);
108
+ const jour = date ? exigerJour(date, 'date') : aujourdhui();
109
+ const tous = await releves(vehicule);
110
+ const km = R.dernierReleve(tous, jour)?.km, rythme = R.kmParJour(tous, jour);
111
+ return (await de('plans', vehicule)).map((p) => R.prochainEntretien(p, { km, date: jour, kmParJour: rythme }))
112
+ .sort((a, b) => (a.le === b.le ? 0 : a.le === null ? 1 : b.le === null ? -1 : a.le < b.le ? -1 : 1));
113
+ }
114
+
105
115
  async function peutPartir(id, { date, conducteur, km } = {}) {
106
116
  const v = await lire(id);
107
117
  const jour = date ? exigerJour(date, 'date') : aujourdhui();
@@ -145,6 +155,22 @@ export function createFlotte({
145
155
  return v;
146
156
  },
147
157
 
158
+ /**
159
+ * La liste du parc, rangée par code : `statut` (un code ou une liste), `type`, `site`, et `q` cherché dans le code,
160
+ * la plaque, la marque et le modèle (sans casse ; espaces et tirets indifférents). Sans `statut`, les réformés
161
+ * n'y sont que si `reformes` est vrai.
162
+ */
163
+ async vehicules({ statut, type, site, q, reformes = false } = {}) {
164
+ const statuts = statut === undefined ? null : [statut].flat();
165
+ for (const s of statuts || []) if (!CODES.statut.includes(s)) refus('DONNEE', `statut inconnu : ${s}`, 400);
166
+ const plat = (s) => String(s ?? '').toLowerCase().replace(/[\s-]+/g, '');
167
+ const cherche = texte(q) ? plat(q) : null;
168
+ return (await P.vehicules.find((v) => (statuts ? statuts.includes(v.statut) : reformes || v.statut !== 'reforme')
169
+ && (!type || v.type === type) && (!site || v.site === site)
170
+ && (!cherche || [v.code, v.immatriculation, v.marque, v.modele].some((c) => plat(c).includes(cherche)))))
171
+ .sort((a, b) => (a.code < b.code ? -1 : a.code > b.code ? 1 : 0)).map((v) => structuredClone(v));
172
+ },
173
+
148
174
  /** Passer une transition du cycle : immobiliser, atelier, remettre, reformer. */
149
175
  async changerStatut(id, transition, ctx = {}) { return transiter(await lire(id), transition, ctx); },
150
176
 
@@ -191,6 +217,9 @@ export function createFlotte({
191
217
  return p;
192
218
  },
193
219
  async plans(vehicule) { await lire(vehicule); return de('plans', vehicule); },
220
+ prochainsEntretiens,
221
+ /** Le plus urgent des prochains entretiens, ou null sans plan (ECR-16.17 : « Prochain entretien »). */
222
+ async prochainEntretien(vehicule, opts = {}) { return (await prochainsEntretiens(vehicule, opts))[0] || null; },
194
223
  /** Les plans dus ou dépassés à `date`, sur les véhicules non réformés. */
195
224
  async entretiensDus({ date } = {}) {
196
225
  const jour = date ? exigerJour(date, 'date') : aujourdhui();
package/src/regles.js CHANGED
@@ -40,6 +40,34 @@ export function etatEntretien(plan, { km, date }) {
40
40
  return { etat: pire.etat, unite: pire.unite, n: pire.n };
41
41
  }
42
42
 
43
+ /**
44
+ * Le rythme du véhicule, en km par jour, mesuré sur les relevés des `fenetre` derniers jours avant `date`.
45
+ * Null sans deux relevés de dates différentes, ou si le compteur n'a pas avancé.
46
+ */
47
+ export function kmParJour(releves, date, fenetre = 90) {
48
+ const debut = ajouterJours(date, -fenetre);
49
+ const dans = [...releves].sort(parDate).filter((r) => r.le >= debut && r.le <= date);
50
+ if (dans.length < 2) return null;
51
+ const a = dans[0], b = dans.at(-1), j = jours(a.le, b.le);
52
+ return j > 0 && b.km > a.km ? arrondi((b.km - a.km) / j) : null;
53
+ }
54
+
55
+ /**
56
+ * Le PROCHAIN entretien d'un plan : l'échéance de chaque pas (km, jours), et la date du premier atteint. Un pas en
57
+ * km devient une date au rythme relevé (`kmParJour`) : la date est alors ESTIMÉE. Sans rythme, un plan au km seul
58
+ * n'a pas de date (`le: null`), seulement ses km restants. `dans` est négatif quand l'échéance est passée.
59
+ */
60
+ export function prochainEntretien(plan, { km, date, kmParJour: rythme = null }) {
61
+ const etat = etatEntretien(plan, { km, date });
62
+ const parKm = plan.tousLesKm > 0 && Number.isFinite(km)
63
+ ? { echeance: (plan.dernierKm || 0) + plan.tousLesKm, restant: (plan.dernierKm || 0) + plan.tousLesKm - km } : null;
64
+ const parJours = plan.tousLesJours > 0 && plan.dernierLe
65
+ ? { echeance: ajouterJours(plan.dernierLe, plan.tousLesJours), restant: jours(date, ajouterJours(plan.dernierLe, plan.tousLesJours)) } : null;
66
+ const leKm = parKm && rythme > 0 ? ajouterJours(date, Math.ceil(parKm.restant / rythme)) : null;
67
+ const le = [leKm, parJours?.echeance].filter(Boolean).sort()[0] || null;
68
+ return { plan: plan.id, libelle: plan.libelle, ...etat, km: parKm, jours: parJours, le, dans: le ? jours(date, le) : null, estime: Boolean(le && le === leKm && le !== parJours?.echeance) };
69
+ }
70
+
43
71
  /** L'échéance qui fait foi pour un type : la plus récente par début (à début égal, la dernière posée). */
44
72
  export function echeanceCourante(echeances, type) {
45
73
  return echeances.filter((e) => e.type === type).sort((a, b) => (a.debut < b.debut ? -1 : a.debut > b.debut ? 1 : 0)).at(-1) || null;
package/src/schemas.js CHANGED
@@ -30,7 +30,7 @@ export const SCHEMAS = Object.freeze([VehiculeSchema, EcheanceSchema, PlanEntret
30
30
 
31
31
  export function register(registry) {
32
32
  registry.register({
33
- manifest: { name: 'flotte', package: '@mostajs/flotte', version: '0.1.0', type: 'business', priority: 45, displayName: 'Flotte (carnet du parc)',
33
+ manifest: { name: 'flotte', package: '@mostajs/flotte', version: '0.2.0', type: 'business', priority: 45, displayName: 'Flotte (carnet du parc)',
34
34
  description: 'Registre des véhicules, cycle, échéances, entretien, relevés, pleins, affectations, coût au km, peutPartir.', register: './src/schemas.js' },
35
35
  schemas: SCHEMAS.map((x) => ({ name: x.name, schema: x })),
36
36
  });