@mostajs/aop-response 0.1.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/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @mostajs/aop-response
2
+
3
+ **Auteur** : Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
4
+
5
+ Dossier de **candidature** aux appels d offres (ORM-first) : **3 pièces** (administratif / mémoire technique / offre financière, §8.4) + **FSM gardée** (draft→preparing→ready→submitted→won/lost) compatible `@mostajs/workflow`. Stack `mosta-aop-stack`.
package/llms.txt ADDED
@@ -0,0 +1,7 @@
1
+ # @mostajs/aop-response — fiche LLM
2
+ > Dossier de candidature AO (ORM-first) : pièces (administratif/technique/financier §8.4) + FSM gardée.
3
+
4
+ ## EXPORTS
5
+ ResponseSchema · REQUIRED_PIECES · emptyPieces() · checklist(pieces)->{complete,missing} · responseWorkflowDef · RESPONSE_STATES · createResponses({repositories:{responses},now}) · createMemoryRepositories()
6
+ API: create({tenderId,clientId,ref}) · setPiece(id,section,key,present) · transition(id,transId)->{ok,dossier|reason,missing?} · checklist(id) · availableTransitions(id) · byTender/byClient.
7
+ FSM: draft→preparing→ready(garde: dossier complet)→submitted→won|lost ; abandon. responseWorkflowDef = compatible @mostajs/workflow defineWorkflow. Stack mosta-aop-stack.
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@mostajs/aop-response",
3
+ "version": "0.1.0",
4
+ "description": "Dossier de candidature aux appels d'offres (ORM-first) : pièces (administratif/technique/financier §8.4), FSM gardée compatible @mostajs/workflow.",
5
+ "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
+ "license": "AGPL-3.0-or-later",
7
+ "type": "module",
8
+ "main": "src/index.js",
9
+ "exports": {
10
+ ".": "./src/index.js"
11
+ },
12
+ "files": [
13
+ "src",
14
+ "README.md",
15
+ "llms.txt"
16
+ ],
17
+ "peerDependencies": {
18
+ "@mostajs/workflow": "*"
19
+ },
20
+ "peerDependenciesMeta": {
21
+ "@mostajs/workflow": {
22
+ "optional": true
23
+ }
24
+ },
25
+ "devDependencies": {
26
+ "@mostajs/mjs-unit": "^0.3.0"
27
+ },
28
+ "scripts": {
29
+ "test": "node test-scripts/unit/aop-response.test.mjs && node examples/run.mjs"
30
+ }
31
+ }
@@ -0,0 +1,37 @@
1
+ // @mostajs/aop-response — dossier de candidature (ORM-first) + transitions gardées (FSM §8.4).
2
+ // Compatible @mostajs/workflow (responseWorkflowDef) ; transition interne si workflow non câblé. @author Dr Hamid MADANI <drmdh@msn.com>
3
+ import { emptyPieces, checklist } from './pieces.js';
4
+ import { responseWorkflowDef } from './workflow-def.js';
5
+ export function createResponses({ repositories, now = () => new Date() } = {}) {
6
+ if (!repositories?.responses) throw new Error('aop-response: repositories.responses requis');
7
+ const repo = repositories.responses;
8
+ const trans = responseWorkflowDef.transitions;
9
+ async function create({ tenderId, clientId = null, ref = null } = {}) {
10
+ if (!tenderId) throw new Error('aop-response: tenderId requis');
11
+ return repo.create({ tenderId, clientId, ref, status: 'draft', pieces: emptyPieces() });
12
+ }
13
+ /** Coche/décoche une pièce. */
14
+ async function setPiece(id, section, key, present = true) {
15
+ const d = await repo.get(id); if (!d) return null;
16
+ const pieces = { ...d.pieces, [section]: { ...(d.pieces?.[section] || {}), [key]: !!present } };
17
+ return repo.update(id, { pieces });
18
+ }
19
+ /** Applique une transition par id, en vérifiant la garde (`when`). */
20
+ async function transition(id, transitionId) {
21
+ const d = await repo.get(id); if (!d) return { ok: false, reason: 'introuvable' };
22
+ const t = trans.find((x) => x.id === transitionId && x.from === d.status);
23
+ if (!t) return { ok: false, reason: `transition '${transitionId}' impossible depuis '${d.status}'` };
24
+ if (t.when && !t.when(d)) return { ok: false, reason: 'garde non satisfaite (dossier incomplet)', missing: checklist(d.pieces).missing };
25
+ const patch = { status: t.to };
26
+ if (t.to === 'submitted') patch.submittedAt = now().toISOString();
27
+ return { ok: true, dossier: await repo.update(id, patch) };
28
+ }
29
+ return {
30
+ create, setPiece, transition,
31
+ get: (id) => repo.get(id), list: () => repo.list(),
32
+ byTender: (tenderId) => repo.find((d) => d.tenderId === tenderId),
33
+ byClient: (clientId) => repo.find((d) => d.clientId === clientId),
34
+ checklist: async (id) => checklist((await repo.get(id))?.pieces),
35
+ availableTransitions: async (id) => { const d = await repo.get(id); return d ? trans.filter((t) => t.from === d.status).map((t) => t.id) : []; },
36
+ };
37
+ }
package/src/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { ResponseSchema } from './schemas.js';
2
+ export { REQUIRED_PIECES, emptyPieces, checklist } from './pieces.js';
3
+ export { responseWorkflowDef, RESPONSE_STATES } from './workflow-def.js';
4
+ export { createResponses } from './aop-response.js';
5
+ export { createMemoryRepositories } from './memory-repo.js';
@@ -0,0 +1,2 @@
1
+ function coll(){const m=new Map();let s=0;return{create:async d=>{const id=String(++s),r={id,...d};m.set(id,r);return r;},get:async id=>m.get(id)||null,list:async()=>[...m.values()],find:async p=>[...m.values()].filter(p),update:async(id,patch)=>{const r=m.get(id);const u={...r,...patch};m.set(id,u);return u;}};}
2
+ export function createMemoryRepositories(){return{responses:coll()};}
package/src/pieces.js ADDED
@@ -0,0 +1,18 @@
1
+ // @mostajs/aop-response — pièces du dossier de candidature (DEVRULES §8.4 #4). @author Dr Hamid MADANI <drmdh@msn.com>
2
+ export const REQUIRED_PIECES = {
3
+ administratif: ['declaration_candidature', 'declaration_probite', 'registre_commerce', 'nif_nis',
4
+ 'attestation_fiscale', 'attestation_cnas', 'attestation_casnos', 'agrements', 'references', 'statuts_pouvoirs'],
5
+ technique: ['memoire_technique'],
6
+ financier: ['bpu', 'dqe'],
7
+ };
8
+ export const emptyPieces = () => ({
9
+ administratif: Object.fromEntries(REQUIRED_PIECES.administratif.map((k) => [k, false])),
10
+ technique: Object.fromEntries(REQUIRED_PIECES.technique.map((k) => [k, false])),
11
+ financier: Object.fromEntries(REQUIRED_PIECES.financier.map((k) => [k, false])),
12
+ });
13
+ /** Checklist : { complete, missing: [...] } sur l'ensemble des pièces requises. */
14
+ export function checklist(pieces = {}) {
15
+ const missing = [];
16
+ for (const [section, keys] of Object.entries(REQUIRED_PIECES)) for (const k of keys) if (!pieces?.[section]?.[k]) missing.push(`${section}.${k}`);
17
+ return { complete: missing.length === 0, missing };
18
+ }
package/src/schemas.js ADDED
@@ -0,0 +1,13 @@
1
+ export const ResponseSchema = {
2
+ name: 'ResponseDossier', collection: 'aop_responses', timestamps: true,
3
+ fields: {
4
+ tenderId: { type: 'string', required: true }, clientId: { type: 'string', default: null },
5
+ ref: { type: 'string', default: null },
6
+ status: { type: 'string', enum: ['draft', 'preparing', 'ready', 'submitted', 'won', 'lost', 'abandoned'], default: 'draft' },
7
+ pieces: { type: 'json', default: {} }, // { administratif{}, technique{}, financier{} }
8
+ financialOffer: { type: 'json', default: {} }, // { bpu:[], dqe:[], total }
9
+ submittedAt: { type: 'date', default: null }, outcome: { type: 'json', default: {} },
10
+ notes: { type: 'text', default: '' },
11
+ },
12
+ indexes: [{ fields: { tenderId: 'asc' } }, { fields: { clientId: 'asc' } }, { fields: { status: 'asc' } }],
13
+ };
@@ -0,0 +1,15 @@
1
+ // FSM du dossier de réponse — compatible @mostajs/workflow (defineWorkflow). @author Dr Hamid MADANI <drmdh@msn.com>
2
+ import { checklist } from './pieces.js';
3
+ export const RESPONSE_STATES = ['draft', 'preparing', 'ready', 'submitted', 'won', 'lost', 'abandoned'];
4
+ export const responseWorkflowDef = {
5
+ initial: 'draft',
6
+ states: RESPONSE_STATES,
7
+ transitions: [
8
+ { id: 'start', from: 'draft', to: 'preparing' },
9
+ { id: 'ready', from: 'preparing', to: 'ready', when: (ctx) => checklist(ctx?.pieces).complete }, // garde : dossier complet
10
+ { id: 'submit', from: 'ready', to: 'submitted' },
11
+ { id: 'win', from: 'submitted', to: 'won' },
12
+ { id: 'lose', from: 'submitted', to: 'lost' },
13
+ { id: 'abandon', from: 'preparing', to: 'abandoned' },
14
+ ],
15
+ };