@mostajs/flotte 0.3.0 → 0.4.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 +16 -2
- package/llms.txt +7 -3
- package/package.json +2 -1
- package/src/flotte.js +47 -8
- package/src/schemas.js +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,14 +3,28 @@
|
|
|
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.4.0] — non publiée
|
|
7
|
+
### Added
|
|
8
|
+
- `poserEcheance` garde `reference` (n° de police, de procès-verbal) et `organisme` ; `piece` accepte un objet
|
|
9
|
+
(`{ nom, empreinte, deposePar, deposeLe }`) : colonne JSON. Essai T-FLO-18.
|
|
10
|
+
- Un relevé garde ses `avertissements` (saut anormal), lisibles dans son historique. Essai T-FLO-19.
|
|
11
|
+
- `modifier(id, champs)` : les champs descriptifs (plaque contrôlée, attributs validés par le type, site, mode,
|
|
12
|
+
`chassis`, `miseEnService`…), jamais le code, le type ni le statut ; inscrit à l'historique
|
|
13
|
+
(`{ le, agent, modification: { champs } }`). `inscrire` accepte `chassis` et `miseEnService`. Essai T-FLO-20.
|
|
14
|
+
- `vehiculeDe(conducteur)` : l'affectation en cours d'un conducteur. Essai T-FLO-21.
|
|
15
|
+
- `pleins(v)` : la liste des pleins, par date (onglet « Pleins et relevés »). Essai T-FLO-22.
|
|
16
|
+
Demandés par les planches A à F de Guerway (`GUERWAY/docs/L0/maquette-flotte/`, inventaire partie 9).
|
|
17
|
+
### Fixed
|
|
18
|
+
- Le manifeste de `register` annonçait la version 0.2.0 dans la 0.3.0 publiée ; il suit désormais la version.
|
|
19
|
+
|
|
20
|
+
## [0.3.0] — 2026-10-24
|
|
7
21
|
### Added
|
|
8
22
|
- `vehicules({ statut?, type?, site?, q?, reformes? })` : la liste du parc, rangée par code ; `statut` est un code
|
|
9
23
|
ou une liste ; `q` est cherché dans le code, la plaque, la marque et le modèle, sans casse, espaces et tirets
|
|
10
24
|
indifférents ; les réformés n'y sont que sur demande. Demandé par le tableau de bord ECR-16 de Guerway, que
|
|
11
25
|
`@mostajs/flotte-ui` construit sans accéder aux dépôts. Essai T-FLO-17.
|
|
12
26
|
|
|
13
|
-
## [0.2.0] —
|
|
27
|
+
## [0.2.0] — jamais publiée seule (contenue dans la 0.3.0)
|
|
14
28
|
### Added
|
|
15
29
|
- `prochainsEntretiens(v, { date? })` et `prochainEntretien(v)` : pour chaque plan, l'échéance de chaque pas (km,
|
|
16
30
|
jours) et la date du premier atteint, rangés du plus urgent au moins urgent. Un pas en km devient une date au rythme
|
package/llms.txt
CHANGED
|
@@ -25,16 +25,20 @@ EXPORTS
|
|
|
25
25
|
API (async ; ctx = { agent })
|
|
26
26
|
inscrire({ code, immatriculation, type, attributs, marque?, site?, mode? }, ctx) ; lire(id)
|
|
27
27
|
vehicules({ statut?, type?, site?, q?, reformes? }) (0.3.0) -> copies rangées par code ; réformés exclus sauf demande
|
|
28
|
+
modifier(id, { immatriculation?, marque?, attributs?, site?, mode?, chassis?, miseEnService?, … }, ctx) (0.4.0) jamais code/type/statut ;
|
|
29
|
+
historique += { le, agent, modification: { champs } } (une entrée SANS `vers` : ce n'est pas un changement de statut)
|
|
30
|
+
vehiculeDe(conducteur) (0.4.0) -> affectation en cours ou null
|
|
28
31
|
changerStatut(id, 'immobiliser'|'atelier'|'remettre'|'reformer', { agent, motif }) motif requis : immobiliser, reformer
|
|
29
|
-
poserEcheance({ vehicule, type, debut, fin?, cout?, piece? }, ctx) fin absente = debut + periodiciteJours
|
|
32
|
+
poserEcheance({ vehicule, type, debut, fin?, cout?, piece?, reference?, organisme? }, ctx) fin absente = debut + periodiciteJours ;
|
|
33
|
+
piece : chaîne ou { nom, empreinte, deposePar, deposeLe } (0.4.0)
|
|
30
34
|
echeances(v) ; aEcheance({ dans?, date? }) -> [{ id, vehicule, type, fin, n, expiree }]
|
|
31
35
|
planifierEntretien({ vehicule, libelle, tousLesKm?, tousLesJours?, toleranceKm?, toleranceJours? }, ctx) ; plans(v) ; entretiensDus({ date? })
|
|
32
36
|
prochainsEntretiens(v, { date? }) / prochainEntretien(v) (0.2.0) -> { plan, libelle, etat, km: { echeance, restant },
|
|
33
37
|
jours: { echeance, restant }, le, dans, estime } le pas en km devient une date au rythme relevé (estime: true)
|
|
34
38
|
ouvrirIntervention({ vehicule, nature, plan?, description?, ouverteLe? }, ctx) corrective => a_l_atelier
|
|
35
39
|
cloreIntervention(id, { km?, cout?, le? }, ctx) recale le plan ; km => relevé ; close => immuable
|
|
36
|
-
relever(v, { km, le?, source?, correction?: { motif, agent } }, ctx) -> relevé + avertissements (SAUT_ANORMAL)
|
|
37
|
-
saisirPlein({ vehicule, le, litres, montant, km, complet }, ctx) vaut relevé ; consommation(v)
|
|
40
|
+
relever(v, { km, le?, source?, correction?: { motif, agent } }, ctx) -> relevé + avertissements (SAUT_ANORMAL), conservés sur le relevé (0.4.0)
|
|
41
|
+
saisirPlein({ vehicule, le, litres, montant, km, complet }, ctx) vaut relevé ; pleins(v) (0.4.0) ; consommation(v)
|
|
38
42
|
affecter(v, conducteur, ctx) ; liberer(v, ctx) ; affectations(v) ; affectationCourante(v)
|
|
39
43
|
peutPartir(v, { date?, conducteur?, km? }) -> { ok, motifs[], avertissements[] } TOUS les motifs, en OBJETS
|
|
40
44
|
disponibles({ date?, type?, attributs? }) ; cout(v, { du, au }) -> { carburant, entretien, echeances, total, km, parKm, nonComptes }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/flotte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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>",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"verbes": [
|
|
62
62
|
"inscrire",
|
|
63
63
|
"vehicules",
|
|
64
|
+
"modifier",
|
|
64
65
|
"changerStatut",
|
|
65
66
|
"peutPartir",
|
|
66
67
|
"prochainEntretien",
|
package/src/flotte.js
CHANGED
|
@@ -20,6 +20,10 @@ const exigerNombre = (n, quoi, { min = 0, strict = false } = {}) => {
|
|
|
20
20
|
if (!Number.isFinite(n) || (strict ? n <= min : n < min)) refus('DONNEE', `${quoi} : nombre ${strict ? '>' : '≥'} ${min} attendu`, 400);
|
|
21
21
|
return n;
|
|
22
22
|
};
|
|
23
|
+
/** Un texte facultatif : absent → null, chaîne → nettoyée, autre chose → refus. */
|
|
24
|
+
const texteOuNul = (x, quoi) => { if (x === undefined || x === null || x === '') return null; if (typeof x !== 'string') refus('DONNEE', `${quoi} : texte attendu`, 400); return x.trim() || null; };
|
|
25
|
+
/** Les champs descriptifs d'un véhicule, modifiables par `modifier` (ni le code, ni le type, ni le statut). */
|
|
26
|
+
const MODIFIABLES = ['immatriculation', 'marque', 'modele', 'annee', 'couleur', 'energie', 'attributs', 'site', 'detenteur', 'mode', 'acquisition', 'chassis', 'miseEnService'];
|
|
23
27
|
const correspond = (motif, valeur) => new RegExp(motif.source, motif.flags.replace(/[gy]/g, '')).test(valeur);
|
|
24
28
|
|
|
25
29
|
/**
|
|
@@ -50,6 +54,12 @@ export function createFlotte({
|
|
|
50
54
|
const maj = async (table, doc, champs) => { await P[table].update(doc.id, champs); return { ...doc, ...structuredClone(champs) }; };
|
|
51
55
|
const de = (table, vehicule) => P[table].find((x) => x.vehicule === vehicule);
|
|
52
56
|
|
|
57
|
+
function plaqueValide(saisie) {
|
|
58
|
+
const p = String(saisie).trim().toUpperCase();
|
|
59
|
+
if (D.immatriculation.length && !D.immatriculation.some((m) => correspond(m, p))) refus('IMMATRICULATION', `immatriculation hors des motifs déclarés : ${p}`, 400);
|
|
60
|
+
return p;
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
async function lire(id) {
|
|
54
64
|
return (await P.vehicules.find((v) => v.id === id))[0] || refus('INCONNU', `véhicule inconnu : ${id}`, 404);
|
|
55
65
|
}
|
|
@@ -79,7 +89,7 @@ export function createFlotte({
|
|
|
79
89
|
return [];
|
|
80
90
|
}
|
|
81
91
|
async function ecrireReleve(vehicule, { km, le, source, correction }, avertissements, agent) {
|
|
82
|
-
const r = await creer('releves', { id: randomUUID(), vehicule, km, le, source, correction: correction ? { motif: correction.motif, agent: correction.agent } : null, saisiPar: agent ?? null });
|
|
92
|
+
const r = await creer('releves', { id: randomUUID(), vehicule, km, le, source, correction: correction ? { motif: correction.motif, agent: correction.agent } : null, saisiPar: agent ?? null, avertissements });
|
|
83
93
|
emettre('releve.saisi', { vehicule, releve: r.id, km, source });
|
|
84
94
|
return { ...r, avertissements };
|
|
85
95
|
}
|
|
@@ -137,10 +147,8 @@ export function createFlotte({
|
|
|
137
147
|
if (!texte(d.immatriculation)) refus('DONNEE', 'immatriculation requise', 400);
|
|
138
148
|
if (!texte(d.type)) refus('DONNEE', 'type requis', 400);
|
|
139
149
|
const attributs = validerAttributs(D, d.type, d.attributs);
|
|
140
|
-
const code = d.code.trim(), immatriculation = d.immatriculation
|
|
141
|
-
|
|
142
|
-
refus('IMMATRICULATION', `immatriculation hors des motifs déclarés : ${immatriculation}`, 400);
|
|
143
|
-
}
|
|
150
|
+
const code = d.code.trim(), immatriculation = plaqueValide(d.immatriculation);
|
|
151
|
+
const miseEnService = d.miseEnService === undefined || d.miseEnService === null ? null : exigerJour(d.miseEnService, 'miseEnService');
|
|
144
152
|
const mode = d.mode ?? 'propre';
|
|
145
153
|
if (!CODES.mode.includes(mode)) refus('DONNEE', `mode inconnu : ${mode}`, 400);
|
|
146
154
|
if ((await P.vehicules.find((v) => v.code === code || v.immatriculation === immatriculation)).length) {
|
|
@@ -149,7 +157,7 @@ export function createFlotte({
|
|
|
149
157
|
const v = await creer('vehicules', {
|
|
150
158
|
id: randomUUID(), code, immatriculation, type: d.type, marque: d.marque ?? null, modele: d.modele ?? null, annee: d.annee ?? null,
|
|
151
159
|
couleur: d.couleur ?? null, energie: d.energie ?? null, attributs, site: d.site ?? null, detenteur: d.detenteur ?? null, mode,
|
|
152
|
-
acquisition: d.acquisition ?? null, statut: cycle.initial, historique: [], inscritLe: aujourdhui(), inscritPar: agent ?? null,
|
|
160
|
+
acquisition: d.acquisition ?? null, chassis: texteOuNul(d.chassis, 'chassis'), miseEnService, statut: cycle.initial, historique: [], inscritLe: aujourdhui(), inscritPar: agent ?? null,
|
|
153
161
|
});
|
|
154
162
|
emettre('vehicule.inscrit', { vehicule: v.id, code, type: v.type });
|
|
155
163
|
return v;
|
|
@@ -174,15 +182,42 @@ export function createFlotte({
|
|
|
174
182
|
/** Passer une transition du cycle : immobiliser, atelier, remettre, reformer. */
|
|
175
183
|
async changerStatut(id, transition, ctx = {}) { return transiter(await lire(id), transition, ctx); },
|
|
176
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Modifier les champs DESCRIPTIFS d'un véhicule (MODIFIABLES) ; ni le code, ni le type, ni le statut (→ changerStatut).
|
|
187
|
+
* La plaque reste contrôlée (motif, unicité), les attributs par le type. La modification est inscrite à
|
|
188
|
+
* l'historique : { le, agent, modification: { champs } }. Un véhicule réformé ne se modifie plus.
|
|
189
|
+
*/
|
|
190
|
+
async modifier(id, champs = {}, { agent } = {}) {
|
|
191
|
+
const v = await lire(id);
|
|
192
|
+
const cles = Object.keys(champs);
|
|
193
|
+
const interdites = cles.filter((k) => !MODIFIABLES.includes(k));
|
|
194
|
+
if (interdites.length) refus('DONNEE', `champ non modifiable : ${interdites.join(', ')}`, 400);
|
|
195
|
+
if (v.statut === 'reforme') refus('TRANSITION', 'un véhicule réformé ne se modifie plus', 422);
|
|
196
|
+
const m = { ...champs };
|
|
197
|
+
if ('immatriculation' in m) {
|
|
198
|
+
m.immatriculation = plaqueValide(m.immatriculation);
|
|
199
|
+
if ((await P.vehicules.find((x) => x.id !== id && x.immatriculation === m.immatriculation)).length) refus('DOUBLON', `immatriculation déjà prise : ${m.immatriculation}`, 409);
|
|
200
|
+
}
|
|
201
|
+
if ('attributs' in m) m.attributs = validerAttributs(D, v.type, { ...v.attributs, ...m.attributs });
|
|
202
|
+
if ('mode' in m && !CODES.mode.includes(m.mode)) refus('DONNEE', `mode inconnu : ${m.mode}`, 400);
|
|
203
|
+
if ('miseEnService' in m && m.miseEnService !== null) exigerJour(m.miseEnService, 'miseEnService');
|
|
204
|
+
if ('chassis' in m) m.chassis = texteOuNul(m.chassis, 'chassis');
|
|
205
|
+
if (!cles.length) return v;
|
|
206
|
+
const r = await maj('vehicules', v, { ...m, historique: [...v.historique, { le: aujourdhui(), agent: agent ?? null, modification: { champs: cles } }] });
|
|
207
|
+
emettre('vehicule.modifie', { vehicule: id, champs: cles });
|
|
208
|
+
return r;
|
|
209
|
+
},
|
|
210
|
+
|
|
177
211
|
// ── Échéances ──
|
|
178
|
-
async poserEcheance({ vehicule, type, debut, fin, piece, cout } = {}, { agent } = {}) {
|
|
212
|
+
async poserEcheance({ vehicule, type, debut, fin, piece, cout, reference, organisme } = {}, { agent } = {}) {
|
|
179
213
|
await lire(vehicule);
|
|
180
214
|
const d = D.echeances[type] || refus('TYPE', `type d'échéance non déclaré : ${type}`, 400);
|
|
181
215
|
exigerJour(debut, 'debut');
|
|
182
216
|
const f = fin === undefined ? R.ajouterJours(debut, d.periodiciteJours) : exigerJour(fin, 'fin');
|
|
183
217
|
if (f <= debut) refus('DONNEE', 'la fin doit suivre le début', 400);
|
|
184
218
|
if (cout !== undefined) exigerNombre(cout, 'cout');
|
|
185
|
-
const
|
|
219
|
+
const ref = texteOuNul(reference, 'reference'), org = texteOuNul(organisme, 'organisme');
|
|
220
|
+
const e = await creer('echeances', { id: randomUUID(), vehicule, type, debut, fin: f, reference: ref, organisme: org, piece: piece ?? null, cout: cout ?? null, poseePar: agent ?? null, poseeLe: aujourdhui() });
|
|
186
221
|
emettre('echeance.posee', { vehicule, echeance: e.id, type, fin: f });
|
|
187
222
|
return e;
|
|
188
223
|
},
|
|
@@ -298,6 +333,8 @@ export function createFlotte({
|
|
|
298
333
|
const r = await ecrireReleve(vehicule, { km, le: jour, source: 'plein' }, avert, agent);
|
|
299
334
|
return { ...p, avertissements: r.avertissements };
|
|
300
335
|
},
|
|
336
|
+
/** Les pleins d'un véhicule, par date. */
|
|
337
|
+
async pleins(vehicule) { await lire(vehicule); return (await de('pleins', vehicule)).sort((a, b) => (a.le < b.le ? -1 : a.le > b.le ? 1 : 0)); },
|
|
301
338
|
async consommation(vehicule) { await lire(vehicule); return R.consommation(await de('pleins', vehicule)); },
|
|
302
339
|
|
|
303
340
|
// ── Affectations ──
|
|
@@ -330,6 +367,8 @@ export function createFlotte({
|
|
|
330
367
|
},
|
|
331
368
|
async affectations(vehicule) { await lire(vehicule); return (await de('affectations', vehicule)).sort((a, b) => (a.du < b.du ? -1 : a.du > b.du ? 1 : 0)); },
|
|
332
369
|
async affectationCourante(vehicule) { await lire(vehicule); return courante(vehicule); },
|
|
370
|
+
/** L'affectation en cours d'un conducteur (le véhicule qu'il tient), ou null. */
|
|
371
|
+
async vehiculeDe(conducteur) { return (await P.affectations.find((a) => a.conducteur === conducteur && !a.au))[0] || null; },
|
|
333
372
|
|
|
334
373
|
// ── Décisions ──
|
|
335
374
|
peutPartir,
|
package/src/schemas.js
CHANGED
|
@@ -4,10 +4,10 @@ const T = { type: 'string' }, TR = { type: 'string', required: true }, N = { typ
|
|
|
4
4
|
|
|
5
5
|
export const VehiculeSchema = s('Vehicule', 'flotte_vehicules', {
|
|
6
6
|
code: { ...TR, unique: true }, immatriculation: { ...TR, unique: true }, type: TR, marque: T, modele: T, annee: N, couleur: T, energie: T,
|
|
7
|
-
attributs: J, site: T, detenteur: T, mode: TR, acquisition: J, statut: TR, historique: J, inscritLe: T, inscritPar: T,
|
|
7
|
+
attributs: J, site: T, detenteur: T, mode: TR, acquisition: J, chassis: T, miseEnService: T, statut: TR, historique: J, inscritLe: T, inscritPar: T,
|
|
8
8
|
}, [{ fields: { code: 'asc' }, unique: true }, { fields: { immatriculation: 'asc' }, unique: true }, { fields: { statut: 'asc', type: 'asc' } }]);
|
|
9
9
|
export const EcheanceSchema = s('Echeance', 'flotte_echeances', {
|
|
10
|
-
vehicule: TR, type: TR, debut: TR, fin: TR,
|
|
10
|
+
vehicule: TR, type: TR, debut: TR, fin: TR, reference: T, organisme: T, piece: J, cout: N, poseePar: T, poseeLe: T,
|
|
11
11
|
}, [{ fields: { vehicule: 'asc', type: 'asc' } }, { fields: { fin: 'asc' } }]);
|
|
12
12
|
export const PlanEntretienSchema = s('PlanEntretien', 'flotte_plans', {
|
|
13
13
|
vehicule: TR, libelle: TR, tousLesKm: N, tousLesJours: N, toleranceKm: N, toleranceJours: N, dernierKm: N, dernierLe: T, creePar: T,
|
|
@@ -17,7 +17,7 @@ export const InterventionSchema = s('Intervention', 'flotte_interventions', {
|
|
|
17
17
|
ouvertePar: T, closePar: T, modifieePar: T,
|
|
18
18
|
}, [{ fields: { numero: 'asc' }, unique: true }, { fields: { vehicule: 'asc', closeLe: 'asc' } }]);
|
|
19
19
|
export const ReleveCompteurSchema = s('ReleveCompteur', 'flotte_releves', {
|
|
20
|
-
vehicule: TR, km: NR, le: TR, source: TR, correction: J, saisiPar: T,
|
|
20
|
+
vehicule: TR, km: NR, le: TR, source: TR, correction: J, saisiPar: T, avertissements: J,
|
|
21
21
|
}, [{ fields: { vehicule: 'asc', le: 'asc' } }]);
|
|
22
22
|
export const PleinSchema = s('Plein', 'flotte_pleins', {
|
|
23
23
|
vehicule: TR, le: TR, litres: NR, montant: NR, km: NR, complet: B, carte: T, saisiPar: T,
|
|
@@ -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.4.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
|
});
|