@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 +15 -1
- package/README.md +5 -3
- package/llms.txt +5 -2
- package/package.json +3 -1
- package/src/flotte.js +29 -0
- package/src/regles.js +28 -0
- package/src/schemas.js +1 -1
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.
|
|
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 à
|
|
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 à
|
|
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.
|
|
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.
|
|
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
|
});
|