/** * Synthetic BA tree for the menu-node tests. * * CRM/ (app) * PIPELINE/ (module, depends=ERP/REFERENCES) — rbac.md with human rows + machine blocks, * entité.md, a use-case rollup pointing at ./clients, pagespecs (Client.list, Devis.list), * .run-snapshot.json * clients/ (section) use-case.md UC-…-CLIENTS-001/002, screen.md SCR-…-CLIENTS-001 * vip/ (resource) use-case.md UC-…-CLIENTS-VIP-001 * devis/ (section) use-case.md UC-…-DEVIS-001 citing UC-…-CLIENTS-001 * BILLING/ (module, depends=CRM/PIPELINE + ## Dépendances) — entité.md `scope cross-module (CRM/PIPELINE)`, * pagespecs/Invoice.list.md with fkTo pipeline.clients + relatedTabs * invoices/ (section) * ERP/ (app) * REFERENCES/ (module) * rapports/ (section) — the XAPP-001 twin */ import { mkdirSync, writeFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { placeholderDocs } from '../../../../../lib/ba-menu-tree.js' export function w(path: string, content: string): void { mkdirSync(dirname(path), { recursive: true }) writeFileSync(path, content, 'utf8') } function node(root: string, level: 'application' | 'module' | 'section' | 'resource', path: string[], label: string, extraIndex = '', contexte = 'Contexte suffisamment long pour ne pas être un stub : qui, quoi, limites — trois phrases au moins pour passer la barre des cent vingt caractères.'): string { const dir = join(root, ...path) const code = path[path.length - 1]! const children = '' w( join(dir, 'index.md'), `\n# ${code} — ${label}\n\n## Contexte\n${contexte}\n- **Sources** : SRC-001 §1\n\n## Hors-périmètre\n_Aucune exclusion connue à ce stade._\n\n## Enfants\n${children}`, ) for (const d of placeholderDocs(level, code)) w(join(dir, d.file), d.content) return dir } function appendChild(indexPath: string, code: string, label: string): void { const { readFileSync } = require('node:fs') as typeof import('node:fs') const before = readFileSync(indexPath, 'utf8') writeFileSync(indexPath, before + `- [${code}](./${code}/index.md) — ${label}\n`, 'utf8') } export const PAGESPEC = (json: Record): string => `# Page\n\n\`\`\`json\n${JSON.stringify(json, null, 2)}\n\`\`\`\n\nCorps humain.\n` export function writeMenuFixture(root: string): void { w(join(root, 'index.md'), '\n# Demo\n\n## Contexte\nProjet de démonstration.\n\n## Hors-périmètre\n_Aucune exclusion connue à ce stade._\n\n## Enfants\n- [CRM](./CRM/index.md) — CRM\n- [ERP](./ERP/index.md) — ERP\n') // --- CRM --------------------------------------------------------------------------- node(root, 'application', ['CRM'], 'Gestion commerciale') appendChild(join(root, 'CRM', 'index.md'), 'PIPELINE', 'Pipeline') appendChild(join(root, 'CRM', 'index.md'), 'BILLING', 'Facturation') const pipeline = node(root, 'module', ['CRM', 'PIPELINE'], 'Pipeline', ' depends=ERP/REFERENCES') appendChild(join(pipeline, 'index.md'), 'clients', 'Clients') appendChild(join(pipeline, 'index.md'), 'devis', 'Devis') w( join(pipeline, 'rbac.md'), [ '', '# RBAC — CRM / PIPELINE', '', '| Acteur | Permission | Portée |', '|---|---|---|', '| BA-001-AC-001 (Commercial) | `pipeline.clients.read` | toutes |', '| BA-001-AC-001 (Commercial) | `pipeline.clients.vip.export` | toutes |', '| BA-001-AC-002 (Manager) | `pipeline.devis.approve` | équipe |', '', '', '| `crm.pipeline.clients.read` | floor |', '', '', '', '| BA-001-AC-002 | `crm.pipeline.clients.lookup` | derived |', '', '', ].join('\n'), ) w(join(pipeline, 'entité.md'), '\n# Modèle — CRM / PIPELINE\n\n### ENT-001 — Client\n- **Relations** : Client *→1 Country — FK CountryId, scope cross-module (ERP/REFERENCES), onDelete restrict.\n') w(join(pipeline, 'use-case.md'), '\n\n> Voir [clients](./clients/use-case.md), [devis](./devis/use-case.md)\n') w(join(pipeline, '.run-snapshot.json'), '{}\n') w( join(pipeline, 'pagespecs', 'Client.list.md'), PAGESPEC({ screenCode: 'SCR-CRM-PIPELINE-CLIENTS-001', appCode: 'crm', module: 'pipeline', section: 'clients', entity: 'Client', view: 'list', permission: 'pipeline.clients.read', linkedUseCases: ['UC-CRM-PIPELINE-CLIENTS-001'], actions: [{ code: 'open', kind: 'navigate', targetRoute: 'routes.clients.detail(item.id)', permission: 'pipeline.clients.read' }], }), ) w( join(pipeline, 'pagespecs', 'Devis.list.md'), PAGESPEC({ screenCode: 'SCR-CRM-PIPELINE-DEVIS-001', appCode: 'crm', module: 'pipeline', section: 'devis', permission: 'pipeline.devis.read', relatedTabs: [{ key: 'clients', relatedModule: 'pipeline', relatedSection: 'clients', permission: 'pipeline.clients.read' }], }), ) const clients = node(root, 'section', ['CRM', 'PIPELINE', 'clients'], 'Clients') appendChild(join(clients, 'index.md'), 'vip', 'VIP') w( join(clients, 'use-case.md'), [ '', '# Cas d\'usage — clients', '', '### UC-CRM-PIPELINE-CLIENTS-001 — Créer un client', '- **Acteur principal** : BA-001-AC-001', '- **Écrans liés** : SCR-CRM-PIPELINE-CLIENTS-001', '', '### UC-CRM-PIPELINE-CLIENTS-002 — Archiver un client', '- **Acteur principal** : BA-001-AC-002', '', ].join('\n'), ) w(join(clients, 'screen.md'), '\n# Écrans — clients\n\n### SCR-CRM-PIPELINE-CLIENTS-001 — Liste des clients\n- **Cas d\'usage liés** : UC-CRM-PIPELINE-CLIENTS-001\n') node(root, 'resource', ['CRM', 'PIPELINE', 'clients', 'vip'], 'VIP', '', 'Clients VIP.') w(join(clients, 'vip', 'use-case.md'), '\n# Cas d\'usage — vip\n\n### UC-CRM-PIPELINE-CLIENTS-VIP-001 — Gérer un VIP\n- **Acteur principal** : BA-001-AC-001\n') const devis = node(root, 'section', ['CRM', 'PIPELINE', 'devis'], 'Devis') w(join(devis, 'use-case.md'), '\n# Cas d\'usage — devis\n\n### UC-CRM-PIPELINE-DEVIS-001 — Émettre un devis\n- **Acteur principal** : BA-001-AC-001\n- **Cas d\'usage liés** : UC-CRM-PIPELINE-CLIENTS-001, UC-CRM-PIPELINE-CLIENTS-002\n') const billing = node(root, 'module', ['CRM', 'BILLING'], 'Facturation', ' depends=CRM/PIPELINE') w( join(billing, 'index.md'), (require('node:fs') as typeof import('node:fs')).readFileSync(join(billing, 'index.md'), 'utf8').replace('## Enfants\n', '## Dépendances\n- [PIPELINE](../PIPELINE/index.md) — factures liées aux clients\n\n## Enfants\n- [invoices](./invoices/index.md) — Factures\n'), ) w(join(billing, 'entité.md'), '\n# Modèle — CRM / BILLING\n\n### ENT-001 — Invoice\n- **Relations** : Invoice *→1 Client — FK ClientId, scope cross-module (CRM/PIPELINE), onDelete restrict.\n') w( join(billing, 'pagespecs', 'Invoice.list.md'), PAGESPEC({ screenCode: 'SCR-CRM-BILLING-INVOICES-001', appCode: 'crm', module: 'billing', section: 'invoices', permission: 'billing.invoices.read', filters: [{ field: 'clientId', fkTo: { entity: 'Client', app: 'crm', module: 'pipeline', navRoute: 'pipeline.clients', apiEndpoint: '/api/pipeline/clients/lookup' } }], relatedTabs: [{ key: 'clients', relatedModule: 'pipeline', relatedSection: 'clients', permission: 'pipeline.clients.read' }], }), ) node(root, 'section', ['CRM', 'BILLING', 'invoices'], 'Factures') // --- ERP --------------------------------------------------------------------------- node(root, 'application', ['ERP'], 'ERP') appendChild(join(root, 'ERP', 'index.md'), 'REFERENCES', 'Référentiels') const refs = node(root, 'module', ['ERP', 'REFERENCES'], 'Référentiels') appendChild(join(refs, 'index.md'), 'rapports', 'Rapports') node(root, 'section', ['ERP', 'REFERENCES', 'rapports'], 'Rapports') }