@atlashub/smartstack-cli 3.8.0 → 3.9.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/dist/index.js +365 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/templates/agents/action.md +1 -0
- package/templates/agents/ba-writer.md +33 -0
- package/templates/agents/explore-codebase.md +1 -0
- package/templates/agents/explore-docs.md +1 -0
- package/templates/agents/fix-grammar.md +1 -0
- package/templates/agents/snipper.md +1 -0
- package/templates/skills/admin/SKILL.md +6 -0
- package/templates/skills/ai-prompt/SKILL.md +32 -136
- package/templates/skills/ai-prompt/steps/step-01-implementation.md +122 -0
- package/templates/skills/apex/SKILL.md +120 -0
- package/templates/skills/apex/_shared.md +86 -0
- package/templates/skills/apex/references/agent-teams-protocol.md +164 -0
- package/templates/skills/apex/references/smartstack-layers.md +173 -0
- package/templates/skills/apex/steps/step-00-init.md +156 -0
- package/templates/skills/apex/steps/step-01-analyze.md +169 -0
- package/templates/skills/apex/steps/step-02-plan.md +160 -0
- package/templates/skills/apex/steps/step-03-execute.md +166 -0
- package/templates/skills/apex/steps/step-04-validate.md +138 -0
- package/templates/skills/apex/steps/step-05-examine.md +124 -0
- package/templates/skills/apex/steps/step-06-resolve.md +105 -0
- package/templates/skills/apex/steps/step-07-tests.md +130 -0
- package/templates/skills/apex/steps/step-08-run-tests.md +115 -0
- package/templates/skills/application/SKILL.md +10 -0
- package/templates/skills/application/references/backend-controller-hierarchy.md +58 -0
- package/templates/skills/application/references/backend-entity-seeding.md +72 -0
- package/templates/skills/application/references/backend-verification.md +88 -0
- package/templates/skills/application/references/frontend-verification.md +111 -0
- package/templates/skills/application/references/nav-fallback-procedure.md +200 -0
- package/templates/skills/application/references/provider-template.md +134 -0
- package/templates/skills/application/references/test-frontend.md +73 -0
- package/templates/skills/application/references/test-prerequisites.md +72 -0
- package/templates/skills/application/steps/step-01-navigation.md +7 -198
- package/templates/skills/application/steps/step-03b-provider.md +4 -128
- package/templates/skills/application/steps/step-04-backend.md +20 -350
- package/templates/skills/application/steps/step-05-frontend.md +12 -101
- package/templates/skills/application/steps/step-07-tests.md +12 -132
- package/templates/skills/business-analyse/SKILL.md +11 -2
- package/templates/skills/business-analyse/html/ba-interactive.html +176 -14
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +1 -0
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +16 -4
- package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +7 -2
- package/templates/skills/business-analyse/html/src/scripts/09-export.js +103 -0
- package/templates/skills/business-analyse/html/src/scripts/10-comments.js +12 -6
- package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +24 -2
- package/templates/skills/business-analyse/html/src/styles/08-review-panel.css +12 -0
- package/templates/skills/business-analyse/html/src/template.html +1 -0
- package/templates/skills/business-analyse/references/cadrage-structure-cards.md +78 -0
- package/templates/skills/business-analyse/references/cadrage-vibe-coding.md +97 -0
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +92 -0
- package/templates/skills/business-analyse/references/deploy-data-build.md +121 -0
- package/templates/skills/business-analyse/references/deploy-modes.md +49 -0
- package/templates/skills/business-analyse/references/handoff-file-templates.md +119 -0
- package/templates/skills/business-analyse/references/handoff-mappings.md +81 -0
- package/templates/skills/business-analyse/references/html-data-mapping.md +10 -2
- package/templates/skills/business-analyse/references/init-schema-deployment.md +65 -0
- package/templates/skills/business-analyse/references/review-data-mapping.md +363 -0
- package/templates/skills/business-analyse/references/spec-auto-inference.md +57 -0
- package/templates/skills/business-analyse/references/ui-dashboard-spec.md +85 -0
- package/templates/skills/business-analyse/references/ui-resource-cards.md +110 -0
- package/templates/skills/business-analyse/references/validate-incremental-html.md +55 -0
- package/templates/skills/business-analyse/steps/step-00-init.md +35 -68
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +5 -194
- package/templates/skills/business-analyse/steps/step-03a-data.md +6 -49
- package/templates/skills/business-analyse/steps/step-03b-ui.md +12 -178
- package/templates/skills/business-analyse/steps/step-03d-validate.md +3 -48
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +9 -104
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +25 -441
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +19 -187
- package/templates/skills/business-analyse/steps/step-06-review.md +277 -0
- package/templates/skills/cc-agent/references/agent-behavior-patterns.md +95 -0
- package/templates/skills/cc-agent/steps/step-02-generate.md +5 -78
- package/templates/skills/check-version/SKILL.md +7 -0
- package/templates/skills/controller/references/controller-code-templates.md +159 -0
- package/templates/skills/controller/references/permission-sync-templates.md +152 -0
- package/templates/skills/controller/steps/step-03-generate.md +6 -158
- package/templates/skills/controller/steps/step-04-perms.md +5 -144
- package/templates/skills/debug/SKILL.md +7 -0
- package/templates/skills/explore/SKILL.md +6 -0
- package/templates/skills/feature-full/SKILL.md +39 -142
- package/templates/skills/feature-full/steps/step-01-implementation.md +120 -0
- package/templates/skills/gitflow/references/init-config-template.md +135 -0
- package/templates/skills/gitflow/references/init-name-normalization.md +103 -0
- package/templates/skills/gitflow/references/plan-template.md +69 -0
- package/templates/skills/gitflow/references/start-efcore-preflight.md +70 -0
- package/templates/skills/gitflow/references/start-local-config.md +110 -0
- package/templates/skills/gitflow/steps/step-init.md +18 -289
- package/templates/skills/gitflow/steps/step-plan.md +6 -63
- package/templates/skills/gitflow/steps/step-start.md +16 -126
- package/templates/skills/mcp/SKILL.md +9 -213
- package/templates/skills/mcp/steps/step-01-healthcheck.md +108 -0
- package/templates/skills/mcp/steps/step-02-tools.md +73 -0
- package/templates/skills/notification/SKILL.md +7 -0
- package/templates/skills/quick-search/SKILL.md +5 -0
- package/templates/skills/ralph-loop/SKILL.md +99 -381
- package/templates/skills/ralph-loop/references/category-rules.md +259 -0
- package/templates/skills/ralph-loop/references/compact-loop.md +182 -0
- package/templates/skills/ralph-loop/references/task-transform-legacy.md +259 -0
- package/templates/skills/ralph-loop/references/team-orchestration.md +189 -0
- package/templates/skills/ralph-loop/steps/step-00-init.md +111 -383
- package/templates/skills/ralph-loop/steps/step-01-task.md +79 -896
- package/templates/skills/ralph-loop/steps/step-02-execute.md +68 -680
- package/templates/skills/ralph-loop/steps/step-03-commit.md +47 -277
- package/templates/skills/ralph-loop/steps/step-04-check.md +124 -607
- package/templates/skills/ralph-loop/steps/step-05-report.md +68 -367
- package/templates/skills/refactor/SKILL.md +12 -176
- package/templates/skills/refactor/steps/step-01-discover.md +60 -0
- package/templates/skills/refactor/steps/step-02-execute.md +67 -0
- package/templates/skills/review-code/SKILL.md +19 -257
- package/templates/skills/review-code/steps/step-01-smartstack.md +96 -0
- package/templates/skills/review-code/steps/step-02-detailed-review.md +80 -0
- package/templates/skills/review-code/steps/step-03-react.md +44 -0
- package/templates/skills/ui-components/SKILL.md +7 -0
- package/templates/skills/utils/SKILL.md +6 -0
- package/templates/skills/validate/SKILL.md +6 -0
- package/templates/skills/validate-feature/SKILL.md +8 -0
- package/templates/skills/workflow/SKILL.md +40 -118
- package/templates/skills/workflow/steps/step-01-implementation.md +84 -0
|
@@ -47,78 +47,11 @@ From previous steps:
|
|
|
47
47
|
|
|
48
48
|
## PRE-REQUISITES
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# Look for existing test project
|
|
56
|
-
ls *Tests*/*.csproj 2>/dev/null || ls tests/*/*.csproj 2>/dev/null
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
If NO test project exists, create one:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
dotnet new xunit -n {SolutionName}.Tests -o tests/{SolutionName}.Tests
|
|
63
|
-
dotnet sln add tests/{SolutionName}.Tests/{SolutionName}.Tests.csproj
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### 2. Verify Required NuGet Packages
|
|
67
|
-
|
|
68
|
-
The test project MUST have these packages:
|
|
69
|
-
|
|
70
|
-
| Package | Purpose |
|
|
71
|
-
|---------|---------|
|
|
72
|
-
| `xunit` | Test framework |
|
|
73
|
-
| `FluentAssertions` | Assertion library |
|
|
74
|
-
| `Moq` | Mocking framework |
|
|
75
|
-
| `Microsoft.AspNetCore.Mvc.Testing` | Integration test support |
|
|
76
|
-
| `Microsoft.EntityFrameworkCore.Sqlite` | SQLite in-memory for REAL integration tests |
|
|
77
|
-
| `Microsoft.Data.Sqlite` | SQLite connection support |
|
|
78
|
-
| `Microsoft.EntityFrameworkCore.InMemory` | In-memory DB for repository tests |
|
|
79
|
-
| `FluentValidation.TestHelper` | Validator testing support |
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
cd tests/{SolutionName}.Tests
|
|
83
|
-
dotnet add package FluentAssertions
|
|
84
|
-
dotnet add package Moq
|
|
85
|
-
dotnet add package Microsoft.AspNetCore.Mvc.Testing
|
|
86
|
-
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
|
|
87
|
-
dotnet add package Microsoft.Data.Sqlite
|
|
88
|
-
dotnet add package Microsoft.EntityFrameworkCore.InMemory
|
|
89
|
-
dotnet add package FluentValidation.TestHelper
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### 3. Add Project References
|
|
93
|
-
|
|
94
|
-
The test project MUST reference the API project (for WebApplicationFactory):
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
cd tests/{SolutionName}.Tests
|
|
98
|
-
dotnet add reference ../../src/{SolutionName}.Api/{SolutionName}.Api.csproj
|
|
99
|
-
dotnet add reference ../../src/{SolutionName}.Application/{SolutionName}.Application.csproj
|
|
100
|
-
dotnet add reference ../../src/{SolutionName}.Domain/{SolutionName}.Domain.csproj
|
|
101
|
-
dotnet add reference ../../src/{SolutionName}.Infrastructure/{SolutionName}.Infrastructure.csproj
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### 4. Ensure Program.cs is Accessible (BLOCKING)
|
|
105
|
-
|
|
106
|
-
The API project's `Program` class must be accessible for `WebApplicationFactory<Program>`.
|
|
107
|
-
|
|
108
|
-
Check if `Program.cs` ends with `public partial class Program { }`. If not, add it:
|
|
109
|
-
|
|
110
|
-
```csharp
|
|
111
|
-
// At the very end of Program.cs
|
|
112
|
-
public partial class Program { }
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
**Alternatively**, add to the API `.csproj`:
|
|
116
|
-
|
|
117
|
-
```xml
|
|
118
|
-
<ItemGroup>
|
|
119
|
-
<InternalsVisibleTo Include="{SolutionName}.Tests" />
|
|
120
|
-
</ItemGroup>
|
|
121
|
-
```
|
|
50
|
+
See [references/test-prerequisites.md](../references/test-prerequisites.md) for the complete setup:
|
|
51
|
+
1. Verify test project exists (create xunit project if missing)
|
|
52
|
+
2. Install required NuGet packages (xunit, FluentAssertions, Moq, Mvc.Testing, EF Sqlite, etc.)
|
|
53
|
+
3. Add project references (API, Application, Domain, Infrastructure)
|
|
54
|
+
4. Ensure Program.cs is accessible for WebApplicationFactory (BLOCKING)
|
|
122
55
|
|
|
123
56
|
---
|
|
124
57
|
|
|
@@ -343,62 +276,13 @@ dotnet test tests/{SolutionName}.Tests/{SolutionName}.Tests.csproj --no-build --
|
|
|
343
276
|
|
|
344
277
|
## FRONTEND TESTS (React Components)
|
|
345
278
|
|
|
346
|
-
|
|
279
|
+
See [references/test-frontend.md](../references/test-frontend.md) for the complete frontend test setup:
|
|
347
280
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
If NOT found, deploy the test infrastructure:
|
|
356
|
-
|
|
357
|
-
1. **Install packages:**
|
|
358
|
-
|
|
359
|
-
```bash
|
|
360
|
-
cd {WebProjectPath}
|
|
361
|
-
npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom msw
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
2. **Copy infrastructure files** from SmartStack templates:
|
|
365
|
-
|
|
366
|
-
| Template Source | Destination |
|
|
367
|
-
|----------------|-------------|
|
|
368
|
-
| `templates/project/test-frontend/vitest.config.ts` | `{WebProjectPath}/vitest.config.ts` |
|
|
369
|
-
| `templates/project/test-frontend/setup.ts` | `{WebProjectPath}/src/test/setup.ts` |
|
|
370
|
-
| `templates/project/test-frontend/test-utils.tsx` | `{WebProjectPath}/src/test/test-utils.tsx` |
|
|
371
|
-
| `templates/project/test-frontend/msw/server.ts` | `{WebProjectPath}/src/test/msw/server.ts` |
|
|
372
|
-
| `templates/project/test-frontend/msw/handlers.ts` | `{WebProjectPath}/src/test/msw/handlers.ts` |
|
|
373
|
-
|
|
374
|
-
3. **Add test script** to `package.json` (if missing):
|
|
375
|
-
|
|
376
|
-
```json
|
|
377
|
-
{
|
|
378
|
-
"scripts": {
|
|
379
|
-
"test": "vitest run",
|
|
380
|
-
"test:watch": "vitest",
|
|
381
|
-
"test:coverage": "vitest run --coverage"
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
### 8. Generate Frontend Tests
|
|
387
|
-
|
|
388
|
-
```
|
|
389
|
-
Tool: mcp__smartstack__scaffold_frontend_tests
|
|
390
|
-
Args:
|
|
391
|
-
name: "{entity_name}"
|
|
392
|
-
components: ["all"]
|
|
393
|
-
apiRoute: "/api/{entity_code}"
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
This generates:
|
|
397
|
-
- `src/pages/{context}/{application}/__tests__/{EntityName}Page.test.tsx` - Page rendering, loading, error states
|
|
398
|
-
- `src/pages/{context}/{application}/__tests__/{EntityName}ListView.test.tsx` - List display, pagination, view toggle
|
|
399
|
-
- `src/pages/{context}/{application}/__tests__/{EntityName}DetailPage.test.tsx` - Detail view, tabs, back navigation
|
|
400
|
-
- `src/hooks/__tests__/use{EntityName}Preferences.test.ts` - Preference get/set
|
|
401
|
-
- `src/services/api/__tests__/{entityName}Api.test.ts` - API client calls with MSW
|
|
281
|
+
### 7-8. Infrastructure & Test Generation
|
|
282
|
+
- Check Vitest + Testing Library + MSW configuration
|
|
283
|
+
- Install packages if missing (`vitest`, `@testing-library/react`, `msw`, etc.)
|
|
284
|
+
- Copy infrastructure templates (vitest.config.ts, setup.ts, test-utils.tsx, MSW handlers)
|
|
285
|
+
- Generate tests via `mcp__smartstack__scaffold_frontend_tests` (Page, ListView, DetailPage, Hooks, API)
|
|
402
286
|
|
|
403
287
|
### 9. Run Frontend Tests (BLOCKING)
|
|
404
288
|
|
|
@@ -407,11 +291,7 @@ cd {WebProjectPath}
|
|
|
407
291
|
npx vitest run --reporter=default
|
|
408
292
|
```
|
|
409
293
|
|
|
410
|
-
**ALL frontend tests MUST pass.**
|
|
411
|
-
1. Read the failure output carefully
|
|
412
|
-
2. Fix the failing tests or the components they test
|
|
413
|
-
3. Re-run until all tests pass
|
|
414
|
-
4. Do NOT proceed to the next step until all tests are green
|
|
294
|
+
**ALL frontend tests MUST pass.** Fix failures before proceeding.
|
|
415
295
|
|
|
416
296
|
---
|
|
417
297
|
|
|
@@ -35,12 +35,16 @@ The skill auto-detects which use case applies by scanning existing features in `
|
|
|
35
35
|
|
|
36
36
|
<parameters>
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
| Flag | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| (aucun) | Mode standard — analyse complete ou mise a jour |
|
|
41
|
+
| `-review` | Mode review — lit `ba-review.json`, cree une nouvelle version avec les corrections appliquees |
|
|
39
42
|
|
|
40
|
-
Step-00 handles
|
|
43
|
+
Step-00 handles detection automatically:
|
|
41
44
|
- **New vs Update**: scans `docs/business/` for existing applications
|
|
42
45
|
- **Single vs Multi-module**: determined during step-02 decomposition
|
|
43
46
|
- **Language**: detected from config or asked once
|
|
47
|
+
- **Review mode**: if `-review` flag, routes directly to step-06
|
|
44
48
|
|
|
45
49
|
</parameters>
|
|
46
50
|
|
|
@@ -108,6 +112,10 @@ When step-00 detects that the description matches an existing application:
|
|
|
108
112
|
- **Step 00:** Detection, locate existing feature, create version N+1
|
|
109
113
|
- **Step 01:** Cadrage delta: what changes, impact on existing
|
|
110
114
|
- **Step 02-05:** Same flow as new, with existing context loaded
|
|
115
|
+
|
|
116
|
+
**Review workflow (`-review` flag):**
|
|
117
|
+
- **Step 00:** Detection, scan for latest application, locate `ba-review.json`
|
|
118
|
+
- **Step 06:** Apply corrections, create new version, reverse-map data, regenerate all artifacts
|
|
111
119
|
</workflow>
|
|
112
120
|
|
|
113
121
|
<state_variables>
|
|
@@ -149,6 +157,7 @@ When step-00 detects that the description matches an existing application:
|
|
|
149
157
|
| 04 | `steps/step-04-consolidation.md` | Opus | Cross-module validation, E2E flows, permissions coherence |
|
|
150
158
|
| 05a | `steps/step-05a-handoff.md` | Sonnet | Handoff: file mapping (7 categories), BR-to-code mapping, API summary, write handoff |
|
|
151
159
|
| 05b | `steps/step-05b-deploy.md` | Sonnet | Deploy: prd.json, progress.txt, manifest, ba-interactive.html, auto-launch ralph-loop |
|
|
160
|
+
| 06 | `steps/step-06-review.md` | Opus | Apply review corrections, create new version, regenerate all artifacts |
|
|
152
161
|
|
|
153
162
|
</step_files>
|
|
154
163
|
|
|
@@ -1380,6 +1380,18 @@ body {
|
|
|
1380
1380
|
font-size: 0.65rem;
|
|
1381
1381
|
}
|
|
1382
1382
|
|
|
1383
|
+
/* Review save button */
|
|
1384
|
+
.btn-review {
|
|
1385
|
+
background: var(--success);
|
|
1386
|
+
color: #fff;
|
|
1387
|
+
border-color: var(--success);
|
|
1388
|
+
font-weight: 600;
|
|
1389
|
+
}
|
|
1390
|
+
.btn-review:hover {
|
|
1391
|
+
background: #16a34a;
|
|
1392
|
+
border-color: #16a34a;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1383
1395
|
@media (max-width: 768px) {
|
|
1384
1396
|
.body.review-open {
|
|
1385
1397
|
grid-template-columns: 1fr;
|
|
@@ -1408,6 +1420,7 @@ body {
|
|
|
1408
1420
|
<div class="header-spacer"></div>
|
|
1409
1421
|
<div class="header-actions">
|
|
1410
1422
|
<button class="btn btn-sm" onclick="saveToLocalStorage()" title="Sauvegarder les modifications dans le navigateur">Sauvegarder</button>
|
|
1423
|
+
<button class="btn btn-sm btn-review" onclick="saveReviewJSON()" title="Sauvegarder les corrections pour creer une nouvelle version">Sauvegarder corrections</button>
|
|
1411
1424
|
<button class="btn btn-sm btn-primary" onclick="exportJSON()" title="Exporter les donnees au format JSON pour l'extraction">Exporter JSON</button>
|
|
1412
1425
|
<button class="btn btn-sm review-toggle-btn" id="reviewToggleBtn" onclick="toggleReviewPanel()" title="Ouvrir/fermer le panneau de review">
|
|
1413
1426
|
Review
|
|
@@ -2009,6 +2022,7 @@ body {
|
|
|
2009
2022
|
============================================ */
|
|
2010
2023
|
const APP_KEY = 'ba-{{APPLICATION_ID}}';
|
|
2011
2024
|
let data = {{FEATURE_DATA}};
|
|
2025
|
+
const ORIGINAL_DATA = JSON.parse(JSON.stringify(data));
|
|
2012
2026
|
const EMBEDDED_ARTIFACTS = {{EMBEDDED_ARTIFACTS}};
|
|
2013
2027
|
|
|
2014
2028
|
// Initialize optional data structures
|
|
@@ -2605,6 +2619,8 @@ function renderAllModuleSpecs() {
|
|
|
2605
2619
|
});
|
|
2606
2620
|
// Restore currently visible section after re-render
|
|
2607
2621
|
restoreCurrentSection();
|
|
2622
|
+
// Re-initialize inline comment buttons on module spec cards
|
|
2623
|
+
if (typeof initInlineComments === 'function') initInlineComments();
|
|
2608
2624
|
}
|
|
2609
2625
|
|
|
2610
2626
|
function renderModuleSpecSection(mod) {
|
|
@@ -3007,6 +3023,17 @@ function renderModuleMockups(code) {
|
|
|
3007
3023
|
}
|
|
3008
3024
|
|
|
3009
3025
|
function getPermRoles() {
|
|
3026
|
+
// Extract roles from actual permission data (handles English role names from feature.json)
|
|
3027
|
+
const rolesFromPerms = [];
|
|
3028
|
+
const seen = new Set();
|
|
3029
|
+
data.modules.forEach(m => {
|
|
3030
|
+
(data.moduleSpecs[m.code]?.permissions || []).forEach(p => {
|
|
3031
|
+
const role = p.split('|')[0];
|
|
3032
|
+
if (role && !seen.has(role)) { seen.add(role); rolesFromPerms.push(role); }
|
|
3033
|
+
});
|
|
3034
|
+
});
|
|
3035
|
+
if (rolesFromPerms.length > 0) return [...rolesFromPerms, ...(data.customRoles || [])];
|
|
3036
|
+
// Fallback: use stakeholder names if no permission data exists
|
|
3010
3037
|
const baseRoles = data.cadrage.stakeholders.length > 0
|
|
3011
3038
|
? data.cadrage.stakeholders.map(s => s.role)
|
|
3012
3039
|
: ['Administrateur', 'Responsable', 'Contributeur', 'Lecteur'];
|
|
@@ -3021,9 +3048,7 @@ function getPermActions() {
|
|
|
3021
3048
|
function renderPermissionGrid(code) {
|
|
3022
3049
|
const roles = getPermRoles();
|
|
3023
3050
|
const actions = getPermActions();
|
|
3024
|
-
const baseRolesCount = data.
|
|
3025
|
-
? data.cadrage.stakeholders.length
|
|
3026
|
-
: 4;
|
|
3051
|
+
const baseRolesCount = roles.length - (data.customRoles || []).length;
|
|
3027
3052
|
const baseActionsCount = 6;
|
|
3028
3053
|
|
|
3029
3054
|
const perms = data.moduleSpecs[code]?.permissions || [];
|
|
@@ -3040,7 +3065,8 @@ function renderPermissionGrid(code) {
|
|
|
3040
3065
|
<td style="font-weight:500;color:var(--text-bright);">${role}${ri >= baseRolesCount ? ' <span onclick="removeCustomRole('+`'${code}','${role}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer ce role">✕</span>' : ''}</td>
|
|
3041
3066
|
${actions.map(action => {
|
|
3042
3067
|
const key = role + '|' + action;
|
|
3043
|
-
const
|
|
3068
|
+
const wildcardKey = role + '|*';
|
|
3069
|
+
const checked = perms.includes(key) || perms.includes(wildcardKey);
|
|
3044
3070
|
return `<td style="text-align:center;"><input type="checkbox" ${checked ? 'checked' : ''} onchange="togglePermission('${code}','${key}',this.checked)" style="cursor:pointer;width:16px;height:16px;"></td>`;
|
|
3045
3071
|
}).join('')}
|
|
3046
3072
|
</tr>
|
|
@@ -3171,10 +3197,15 @@ function renderConsolPermissions() {
|
|
|
3171
3197
|
roles.forEach(role => {
|
|
3172
3198
|
html += `<tr><td style="font-weight:500;color:var(--text-bright);">${role}</td>`;
|
|
3173
3199
|
data.modules.forEach(m => {
|
|
3174
|
-
const
|
|
3200
|
+
const allPerms = data.moduleSpecs[m.code]?.permissions || [];
|
|
3201
|
+
const hasWildcard = allPerms.includes(role + '|*');
|
|
3202
|
+
const perms = hasWildcard
|
|
3203
|
+
? getPermActions()
|
|
3204
|
+
: allPerms.filter(p => p.startsWith(role + '|'));
|
|
3175
3205
|
const count = perms.length;
|
|
3176
3206
|
const color = count > 4 ? 'var(--success)' : count > 2 ? 'var(--warning)' : count > 0 ? 'var(--text-muted)' : 'var(--border)';
|
|
3177
|
-
|
|
3207
|
+
const label = hasWildcard ? 'Tous' : (count > 0 ? count + ' droits' : 'Aucun');
|
|
3208
|
+
html += `<td style="text-align:center;color:${color};font-weight:600;">${label}</td>`;
|
|
3178
3209
|
});
|
|
3179
3210
|
html += '</tr>';
|
|
3180
3211
|
});
|
|
@@ -3446,6 +3477,109 @@ function exportJSON() {
|
|
|
3446
3477
|
showNotification('Export JSON telecharge');
|
|
3447
3478
|
}
|
|
3448
3479
|
|
|
3480
|
+
/* ============================================
|
|
3481
|
+
SAVE REVIEW JSON (corrections for new version)
|
|
3482
|
+
============================================ */
|
|
3483
|
+
function detectChanges(original, current) {
|
|
3484
|
+
const changes = { cadrage: false, modulesAdded: [], modulesRemoved: [], modulesModified: [], commentsCount: 0 };
|
|
3485
|
+
|
|
3486
|
+
// Cadrage changes
|
|
3487
|
+
if (JSON.stringify(original.cadrage) !== JSON.stringify(current.cadrage)) {
|
|
3488
|
+
changes.cadrage = true;
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
// Module changes
|
|
3492
|
+
const origCodes = (original.modules || []).map(m => m.code);
|
|
3493
|
+
const currCodes = (current.modules || []).map(m => m.code);
|
|
3494
|
+
changes.modulesAdded = currCodes.filter(c => !origCodes.includes(c));
|
|
3495
|
+
changes.modulesRemoved = origCodes.filter(c => !currCodes.includes(c));
|
|
3496
|
+
currCodes.filter(c => origCodes.includes(c)).forEach(code => {
|
|
3497
|
+
const origSpec = JSON.stringify(original.moduleSpecs?.[code] || {});
|
|
3498
|
+
const currSpec = JSON.stringify(current.moduleSpecs?.[code] || {});
|
|
3499
|
+
if (origSpec !== currSpec) changes.modulesModified.push(code);
|
|
3500
|
+
});
|
|
3501
|
+
|
|
3502
|
+
// Comments count
|
|
3503
|
+
changes.commentsCount = (current.comments || []).length;
|
|
3504
|
+
|
|
3505
|
+
return changes;
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
function saveReviewJSON() {
|
|
3509
|
+
// Collect all editable fields (same as exportJSON)
|
|
3510
|
+
document.querySelectorAll('.editable[data-field]').forEach(el => {
|
|
3511
|
+
setNestedValue(data, 'cadrage.' + el.dataset.field, el.textContent.trim());
|
|
3512
|
+
});
|
|
3513
|
+
document.querySelectorAll('.editable[data-module-field]').forEach(el => {
|
|
3514
|
+
const code = el.dataset.moduleCode;
|
|
3515
|
+
const field = el.dataset.moduleField;
|
|
3516
|
+
if (data.moduleSpecs[code]) {
|
|
3517
|
+
data.moduleSpecs[code][field] = el.textContent.trim();
|
|
3518
|
+
}
|
|
3519
|
+
});
|
|
3520
|
+
|
|
3521
|
+
const changes = detectChanges(ORIGINAL_DATA, data);
|
|
3522
|
+
const hasChanges = changes.cadrage || changes.modulesAdded.length > 0
|
|
3523
|
+
|| changes.modulesRemoved.length > 0 || changes.modulesModified.length > 0
|
|
3524
|
+
|| changes.commentsCount > 0;
|
|
3525
|
+
|
|
3526
|
+
// Build review export with metadata envelope
|
|
3527
|
+
const reviewData = {
|
|
3528
|
+
_reviewMeta: {
|
|
3529
|
+
sourceVersion: data.metadata.version || '1.0',
|
|
3530
|
+
sourceApplicationId: data.metadata.applicationId || '',
|
|
3531
|
+
exportedAt: new Date().toISOString(),
|
|
3532
|
+
hasChanges: hasChanges,
|
|
3533
|
+
changeSummary: {
|
|
3534
|
+
cadrageModified: changes.cadrage,
|
|
3535
|
+
modulesAdded: changes.modulesAdded,
|
|
3536
|
+
modulesRemoved: changes.modulesRemoved,
|
|
3537
|
+
modulesModified: changes.modulesModified,
|
|
3538
|
+
commentsCount: changes.commentsCount
|
|
3539
|
+
}
|
|
3540
|
+
},
|
|
3541
|
+
metadata: data.metadata,
|
|
3542
|
+
cadrage: data.cadrage,
|
|
3543
|
+
modules: data.modules,
|
|
3544
|
+
dependencies: data.dependencies,
|
|
3545
|
+
moduleSpecifications: {},
|
|
3546
|
+
consolidation: data.consolidation,
|
|
3547
|
+
wireframeComments: data.wireframeComments,
|
|
3548
|
+
specComments: data.specComments,
|
|
3549
|
+
customRoles: data.customRoles,
|
|
3550
|
+
customActions: data.customActions,
|
|
3551
|
+
comments: data.comments
|
|
3552
|
+
};
|
|
3553
|
+
|
|
3554
|
+
// Structure module specs (same logic as exportJSON)
|
|
3555
|
+
data.modules.forEach(m => {
|
|
3556
|
+
const spec = data.moduleSpecs[m.code] || {};
|
|
3557
|
+
reviewData.moduleSpecifications[m.code] = {
|
|
3558
|
+
module: m,
|
|
3559
|
+
useCases: (spec.useCases || []).map((uc, i) => ({
|
|
3560
|
+
id: 'UC-' + String(i + 1).padStart(3, '0'),
|
|
3561
|
+
...uc
|
|
3562
|
+
})),
|
|
3563
|
+
businessRules: (spec.businessRules || []).map((br, i) => ({
|
|
3564
|
+
id: 'BR-' + br.category.toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0'),
|
|
3565
|
+
...br
|
|
3566
|
+
})),
|
|
3567
|
+
entities: spec.entities || [],
|
|
3568
|
+
permissions: spec.permissions || [],
|
|
3569
|
+
notes: spec.notes || ''
|
|
3570
|
+
};
|
|
3571
|
+
});
|
|
3572
|
+
|
|
3573
|
+
const blob = new Blob([JSON.stringify(reviewData, null, 2)], { type: 'application/json' });
|
|
3574
|
+
const url = URL.createObjectURL(blob);
|
|
3575
|
+
const a = document.createElement('a');
|
|
3576
|
+
a.href = url;
|
|
3577
|
+
a.download = 'ba-review.json';
|
|
3578
|
+
a.click();
|
|
3579
|
+
URL.revokeObjectURL(url);
|
|
3580
|
+
showNotification('ba-review.json telecharge — sauvegardez-le a cote du HTML');
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3449
3583
|
|
|
3450
3584
|
/* --- 10-comments.js --- */
|
|
3451
3585
|
/* ============================================
|
|
@@ -3463,14 +3597,20 @@ function exportJSON() {
|
|
|
3463
3597
|
*/
|
|
3464
3598
|
|
|
3465
3599
|
function initInlineComments() {
|
|
3466
|
-
// Add comment buttons under each card and uc-item
|
|
3600
|
+
// Add comment buttons under each card and uc-item (direct children of sections)
|
|
3467
3601
|
document.querySelectorAll('.section').forEach(section => {
|
|
3468
3602
|
const sectionId = section.id;
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3603
|
+
section.querySelectorAll(':scope > .card, :scope > .uc-item').forEach((card, index) => {
|
|
3604
|
+
if (card.querySelector('.comment-btn-container')) return;
|
|
3605
|
+
card.appendChild(createCommentUI(sectionId, index));
|
|
3606
|
+
});
|
|
3607
|
+
});
|
|
3608
|
+
// Second pass: nested list containers in module specs (ucList-*, brList-*, entList-*)
|
|
3609
|
+
document.querySelectorAll('[id^="ucList-"], [id^="brList-"], [id^="entList-"]').forEach(list => {
|
|
3610
|
+
const listId = list.id;
|
|
3611
|
+
list.querySelectorAll(':scope > .uc-item, :scope > .entity-block, :scope > div').forEach((item, index) => {
|
|
3612
|
+
if (item.querySelector('.comment-btn-container')) return;
|
|
3613
|
+
item.appendChild(createCommentUI(listId, index));
|
|
3474
3614
|
});
|
|
3475
3615
|
});
|
|
3476
3616
|
}
|
|
@@ -3738,12 +3878,34 @@ function getSectionLabel(sectionId) {
|
|
|
3738
3878
|
const mod = data.modules.find(m => m.code === code);
|
|
3739
3879
|
return mod ? mod.name : code;
|
|
3740
3880
|
}
|
|
3881
|
+
// Handle list-based sectionIds (ucList-*, brList-*, entList-*)
|
|
3882
|
+
const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
|
|
3883
|
+
if (listMatch) {
|
|
3884
|
+
const tabLabels = { uc: 'Cas d\'utilisation', br: 'Regles metier', ent: 'Donnees' };
|
|
3885
|
+
const mod = data.modules.find(m => m.code === listMatch[2]);
|
|
3886
|
+
const modName = mod ? mod.name : listMatch[2];
|
|
3887
|
+
return modName + ' > ' + (tabLabels[listMatch[1]] || listMatch[1]);
|
|
3888
|
+
}
|
|
3741
3889
|
return sectionId;
|
|
3742
3890
|
}
|
|
3743
3891
|
|
|
3744
3892
|
function navigateToComment(sectionId, cardIndex) {
|
|
3745
|
-
|
|
3746
|
-
|
|
3893
|
+
// Handle list-based sectionIds (ucList-*, brList-*, entList-*) → navigate to module + tab
|
|
3894
|
+
const listMatch = sectionId.match(/^(uc|br|ent)List-(.+)$/);
|
|
3895
|
+
if (listMatch) {
|
|
3896
|
+
const [, tabType, moduleCode] = listMatch;
|
|
3897
|
+
showSection('module-spec-' + moduleCode);
|
|
3898
|
+
setTimeout(() => {
|
|
3899
|
+
switchTab(moduleCode, tabType);
|
|
3900
|
+
scrollToCommentThread(sectionId, cardIndex);
|
|
3901
|
+
}, 150);
|
|
3902
|
+
} else {
|
|
3903
|
+
showSection(sectionId);
|
|
3904
|
+
scrollToCommentThread(sectionId, cardIndex);
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
function scrollToCommentThread(sectionId, cardIndex) {
|
|
3747
3909
|
setTimeout(() => {
|
|
3748
3910
|
const thread = document.getElementById('comment-thread-' + sectionId + '-' + cardIndex);
|
|
3749
3911
|
if (thread && !thread.classList.contains('visible')) {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
============================================ */
|
|
4
4
|
const APP_KEY = 'ba-{{APPLICATION_ID}}';
|
|
5
5
|
let data = {{FEATURE_DATA}};
|
|
6
|
+
const ORIGINAL_DATA = JSON.parse(JSON.stringify(data));
|
|
6
7
|
const EMBEDDED_ARTIFACTS = {{EMBEDDED_ARTIFACTS}};
|
|
7
8
|
|
|
8
9
|
// Initialize optional data structures
|
|
@@ -17,6 +17,8 @@ function renderAllModuleSpecs() {
|
|
|
17
17
|
});
|
|
18
18
|
// Restore currently visible section after re-render
|
|
19
19
|
restoreCurrentSection();
|
|
20
|
+
// Re-initialize inline comment buttons on module spec cards
|
|
21
|
+
if (typeof initInlineComments === 'function') initInlineComments();
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
function renderModuleSpecSection(mod) {
|
|
@@ -419,6 +421,17 @@ function renderModuleMockups(code) {
|
|
|
419
421
|
}
|
|
420
422
|
|
|
421
423
|
function getPermRoles() {
|
|
424
|
+
// Extract roles from actual permission data (handles English role names from feature.json)
|
|
425
|
+
const rolesFromPerms = [];
|
|
426
|
+
const seen = new Set();
|
|
427
|
+
data.modules.forEach(m => {
|
|
428
|
+
(data.moduleSpecs[m.code]?.permissions || []).forEach(p => {
|
|
429
|
+
const role = p.split('|')[0];
|
|
430
|
+
if (role && !seen.has(role)) { seen.add(role); rolesFromPerms.push(role); }
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
if (rolesFromPerms.length > 0) return [...rolesFromPerms, ...(data.customRoles || [])];
|
|
434
|
+
// Fallback: use stakeholder names if no permission data exists
|
|
422
435
|
const baseRoles = data.cadrage.stakeholders.length > 0
|
|
423
436
|
? data.cadrage.stakeholders.map(s => s.role)
|
|
424
437
|
: ['Administrateur', 'Responsable', 'Contributeur', 'Lecteur'];
|
|
@@ -433,9 +446,7 @@ function getPermActions() {
|
|
|
433
446
|
function renderPermissionGrid(code) {
|
|
434
447
|
const roles = getPermRoles();
|
|
435
448
|
const actions = getPermActions();
|
|
436
|
-
const baseRolesCount = data.
|
|
437
|
-
? data.cadrage.stakeholders.length
|
|
438
|
-
: 4;
|
|
449
|
+
const baseRolesCount = roles.length - (data.customRoles || []).length;
|
|
439
450
|
const baseActionsCount = 6;
|
|
440
451
|
|
|
441
452
|
const perms = data.moduleSpecs[code]?.permissions || [];
|
|
@@ -452,7 +463,8 @@ function renderPermissionGrid(code) {
|
|
|
452
463
|
<td style="font-weight:500;color:var(--text-bright);">${role}${ri >= baseRolesCount ? ' <span onclick="removeCustomRole('+`'${code}','${role}'`+')" style="cursor:pointer;color:var(--danger);font-size:0.7rem;" title="Supprimer ce role">✕</span>' : ''}</td>
|
|
453
464
|
${actions.map(action => {
|
|
454
465
|
const key = role + '|' + action;
|
|
455
|
-
const
|
|
466
|
+
const wildcardKey = role + '|*';
|
|
467
|
+
const checked = perms.includes(key) || perms.includes(wildcardKey);
|
|
456
468
|
return `<td style="text-align:center;"><input type="checkbox" ${checked ? 'checked' : ''} onchange="togglePermission('${code}','${key}',this.checked)" style="cursor:pointer;width:16px;height:16px;"></td>`;
|
|
457
469
|
}).join('')}
|
|
458
470
|
</tr>
|
|
@@ -38,10 +38,15 @@ function renderConsolPermissions() {
|
|
|
38
38
|
roles.forEach(role => {
|
|
39
39
|
html += `<tr><td style="font-weight:500;color:var(--text-bright);">${role}</td>`;
|
|
40
40
|
data.modules.forEach(m => {
|
|
41
|
-
const
|
|
41
|
+
const allPerms = data.moduleSpecs[m.code]?.permissions || [];
|
|
42
|
+
const hasWildcard = allPerms.includes(role + '|*');
|
|
43
|
+
const perms = hasWildcard
|
|
44
|
+
? getPermActions()
|
|
45
|
+
: allPerms.filter(p => p.startsWith(role + '|'));
|
|
42
46
|
const count = perms.length;
|
|
43
47
|
const color = count > 4 ? 'var(--success)' : count > 2 ? 'var(--warning)' : count > 0 ? 'var(--text-muted)' : 'var(--border)';
|
|
44
|
-
|
|
48
|
+
const label = hasWildcard ? 'Tous' : (count > 0 ? count + ' droits' : 'Aucun');
|
|
49
|
+
html += `<td style="text-align:center;color:${color};font-weight:600;">${label}</td>`;
|
|
45
50
|
});
|
|
46
51
|
html += '</tr>';
|
|
47
52
|
});
|
|
@@ -63,3 +63,106 @@ function exportJSON() {
|
|
|
63
63
|
URL.revokeObjectURL(url);
|
|
64
64
|
showNotification('Export JSON telecharge');
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
/* ============================================
|
|
68
|
+
SAVE REVIEW JSON (corrections for new version)
|
|
69
|
+
============================================ */
|
|
70
|
+
function detectChanges(original, current) {
|
|
71
|
+
const changes = { cadrage: false, modulesAdded: [], modulesRemoved: [], modulesModified: [], commentsCount: 0 };
|
|
72
|
+
|
|
73
|
+
// Cadrage changes
|
|
74
|
+
if (JSON.stringify(original.cadrage) !== JSON.stringify(current.cadrage)) {
|
|
75
|
+
changes.cadrage = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Module changes
|
|
79
|
+
const origCodes = (original.modules || []).map(m => m.code);
|
|
80
|
+
const currCodes = (current.modules || []).map(m => m.code);
|
|
81
|
+
changes.modulesAdded = currCodes.filter(c => !origCodes.includes(c));
|
|
82
|
+
changes.modulesRemoved = origCodes.filter(c => !currCodes.includes(c));
|
|
83
|
+
currCodes.filter(c => origCodes.includes(c)).forEach(code => {
|
|
84
|
+
const origSpec = JSON.stringify(original.moduleSpecs?.[code] || {});
|
|
85
|
+
const currSpec = JSON.stringify(current.moduleSpecs?.[code] || {});
|
|
86
|
+
if (origSpec !== currSpec) changes.modulesModified.push(code);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Comments count
|
|
90
|
+
changes.commentsCount = (current.comments || []).length;
|
|
91
|
+
|
|
92
|
+
return changes;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function saveReviewJSON() {
|
|
96
|
+
// Collect all editable fields (same as exportJSON)
|
|
97
|
+
document.querySelectorAll('.editable[data-field]').forEach(el => {
|
|
98
|
+
setNestedValue(data, 'cadrage.' + el.dataset.field, el.textContent.trim());
|
|
99
|
+
});
|
|
100
|
+
document.querySelectorAll('.editable[data-module-field]').forEach(el => {
|
|
101
|
+
const code = el.dataset.moduleCode;
|
|
102
|
+
const field = el.dataset.moduleField;
|
|
103
|
+
if (data.moduleSpecs[code]) {
|
|
104
|
+
data.moduleSpecs[code][field] = el.textContent.trim();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const changes = detectChanges(ORIGINAL_DATA, data);
|
|
109
|
+
const hasChanges = changes.cadrage || changes.modulesAdded.length > 0
|
|
110
|
+
|| changes.modulesRemoved.length > 0 || changes.modulesModified.length > 0
|
|
111
|
+
|| changes.commentsCount > 0;
|
|
112
|
+
|
|
113
|
+
// Build review export with metadata envelope
|
|
114
|
+
const reviewData = {
|
|
115
|
+
_reviewMeta: {
|
|
116
|
+
sourceVersion: data.metadata.version || '1.0',
|
|
117
|
+
sourceApplicationId: data.metadata.applicationId || '',
|
|
118
|
+
exportedAt: new Date().toISOString(),
|
|
119
|
+
hasChanges: hasChanges,
|
|
120
|
+
changeSummary: {
|
|
121
|
+
cadrageModified: changes.cadrage,
|
|
122
|
+
modulesAdded: changes.modulesAdded,
|
|
123
|
+
modulesRemoved: changes.modulesRemoved,
|
|
124
|
+
modulesModified: changes.modulesModified,
|
|
125
|
+
commentsCount: changes.commentsCount
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
metadata: data.metadata,
|
|
129
|
+
cadrage: data.cadrage,
|
|
130
|
+
modules: data.modules,
|
|
131
|
+
dependencies: data.dependencies,
|
|
132
|
+
moduleSpecifications: {},
|
|
133
|
+
consolidation: data.consolidation,
|
|
134
|
+
wireframeComments: data.wireframeComments,
|
|
135
|
+
specComments: data.specComments,
|
|
136
|
+
customRoles: data.customRoles,
|
|
137
|
+
customActions: data.customActions,
|
|
138
|
+
comments: data.comments
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Structure module specs (same logic as exportJSON)
|
|
142
|
+
data.modules.forEach(m => {
|
|
143
|
+
const spec = data.moduleSpecs[m.code] || {};
|
|
144
|
+
reviewData.moduleSpecifications[m.code] = {
|
|
145
|
+
module: m,
|
|
146
|
+
useCases: (spec.useCases || []).map((uc, i) => ({
|
|
147
|
+
id: 'UC-' + String(i + 1).padStart(3, '0'),
|
|
148
|
+
...uc
|
|
149
|
+
})),
|
|
150
|
+
businessRules: (spec.businessRules || []).map((br, i) => ({
|
|
151
|
+
id: 'BR-' + br.category.toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0'),
|
|
152
|
+
...br
|
|
153
|
+
})),
|
|
154
|
+
entities: spec.entities || [],
|
|
155
|
+
permissions: spec.permissions || [],
|
|
156
|
+
notes: spec.notes || ''
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const blob = new Blob([JSON.stringify(reviewData, null, 2)], { type: 'application/json' });
|
|
161
|
+
const url = URL.createObjectURL(blob);
|
|
162
|
+
const a = document.createElement('a');
|
|
163
|
+
a.href = url;
|
|
164
|
+
a.download = 'ba-review.json';
|
|
165
|
+
a.click();
|
|
166
|
+
URL.revokeObjectURL(url);
|
|
167
|
+
showNotification('ba-review.json telecharge — sauvegardez-le a cote du HTML');
|
|
168
|
+
}
|