@atlashub/smartstack-cli 1.11.0 → 1.13.1
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/.documentation/agents.html +7 -2
- package/.documentation/apex.html +7 -2
- package/.documentation/business-analyse.html +7 -2
- package/.documentation/cli-commands.html +871 -0
- package/.documentation/commands.html +7 -2
- package/.documentation/efcore.html +7 -2
- package/.documentation/gitflow.html +7 -2
- package/.documentation/hooks.html +7 -2
- package/.documentation/index.html +7 -2
- package/.documentation/init.html +7 -2
- package/.documentation/installation.html +7 -2
- package/.documentation/ralph-loop.html +7 -2
- package/.documentation/test-web.html +7 -2
- package/dist/index.js +1932 -336
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/templates/agents/efcore/squash.md +67 -31
- package/templates/agents/gitflow/finish.md +68 -56
- package/templates/commands/business-analyse/0-orchestrate.md +72 -556
- package/templates/commands/business-analyse/1-init.md +23 -193
- package/templates/commands/business-analyse/2-discover.md +85 -462
- package/templates/commands/business-analyse/3-analyse.md +40 -342
- package/templates/commands/business-analyse/4-specify.md +72 -537
- package/templates/commands/business-analyse/5-validate.md +43 -237
- package/templates/commands/business-analyse/6-handoff.md +93 -682
- package/templates/commands/business-analyse/7-doc-html.md +45 -544
- package/templates/commands/business-analyse/_shared.md +176 -0
- package/templates/commands/business-analyse/bug.md +50 -257
- package/templates/commands/business-analyse/change-request.md +59 -283
- package/templates/commands/business-analyse/hotfix.md +36 -120
- package/templates/commands/business-analyse.md +55 -574
- package/templates/commands/efcore/_shared.md +206 -0
- package/templates/commands/efcore/conflicts.md +39 -201
- package/templates/commands/efcore/db-deploy.md +28 -237
- package/templates/commands/efcore/db-reset.md +41 -390
- package/templates/commands/efcore/db-seed.md +44 -323
- package/templates/commands/efcore/db-status.md +31 -210
- package/templates/commands/efcore/migration.md +45 -368
- package/templates/commands/efcore/rebase-snapshot.md +38 -241
- package/templates/commands/efcore/scan.md +35 -204
- package/templates/commands/efcore/squash.md +158 -251
- package/templates/commands/efcore.md +49 -177
- package/templates/commands/gitflow/1-init.md +94 -1318
- package/templates/commands/gitflow/10-start.md +86 -990
- package/templates/commands/gitflow/11-finish.md +264 -454
- package/templates/commands/gitflow/12-cleanup.md +40 -213
- package/templates/commands/gitflow/2-status.md +51 -386
- package/templates/commands/gitflow/3-commit.md +108 -801
- package/templates/commands/gitflow/4-plan.md +42 -13
- package/templates/commands/gitflow/5-exec.md +60 -5
- package/templates/commands/gitflow/6-abort.md +54 -277
- package/templates/commands/gitflow/7-pull-request.md +74 -717
- package/templates/commands/gitflow/8-review.md +51 -178
- package/templates/commands/gitflow/9-merge.md +74 -404
- package/templates/commands/gitflow/_shared.md +196 -0
- package/templates/commands/quickstart.md +154 -0
- package/templates/commands/ralph-loop/ralph-loop.md +104 -2
- package/templates/hooks/hooks.json +13 -0
- package/templates/hooks/ralph-mcp-logger.sh +46 -0
- package/templates/hooks/ralph-session-end.sh +69 -0
- package/templates/ralph/README.md +91 -0
- package/templates/ralph/ralph.config.yaml +113 -0
- package/templates/scripts/setup-ralph-loop.sh +173 -0
- package/templates/skills/_shared.md +117 -0
- package/templates/skills/ai-prompt/SKILL.md +87 -654
- package/templates/skills/application/SKILL.md +76 -499
- package/templates/skills/controller/SKILL.md +38 -165
- package/templates/skills/documentation/SKILL.md +2 -1
- package/templates/skills/feature-full/SKILL.md +107 -732
- package/templates/skills/notification/SKILL.md +85 -474
- package/templates/skills/ui-components/SKILL.md +62 -762
- package/templates/skills/workflow/SKILL.md +85 -489
- package/templates/commands/gitflow/rescue.md +0 -867
- package/templates/skills/business-analyse/SKILL.md +0 -191
|
@@ -12,827 +12,202 @@ description: |
|
|
|
12
12
|
|
|
13
13
|
# Skill Feature Full SmartStack
|
|
14
14
|
|
|
15
|
-
> **OneShot Development:**
|
|
16
|
-
> une feature complete avec la meilleure experience utilisateur possible.
|
|
15
|
+
> **OneShot Development:** Orchestre tous les skills pour une feature complete.
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
**Référence:** [_shared.md](../_shared.md) pour architecture, permissions, i18n
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
## QUAND CE SKILL S'ACTIVE
|
|
21
20
|
|
|
22
21
|
| Declencheur | Exemple |
|
|
23
22
|
|-------------|---------|
|
|
24
23
|
| Feature complete | "Cree un module de gestion des produits" |
|
|
25
|
-
| OneShot | "Implemente la feature de A a Z" |
|
|
26
24
|
| Full-stack | "Cree le backend et le frontend pour..." |
|
|
27
|
-
| UX focus | "Je veux une experience utilisateur complete" |
|
|
28
25
|
| Integration | "Avec notifications et emails automatiques" |
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
## ARCHITECTURE FEATURE COMPLETE
|
|
27
|
+
## FLOW COMPLET
|
|
33
28
|
|
|
34
29
|
```
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
├─────────────────────────────────────────────────────────────────────────────┤
|
|
38
|
-
│ │
|
|
39
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
40
|
-
│ │ DOMAIN LAYER │ │
|
|
41
|
-
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
42
|
-
│ │ │ Entity │ │ Enums │ │ Events │ │ Factory │ │ Specs │ │ │
|
|
43
|
-
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
44
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
45
|
-
│ │ │
|
|
46
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
47
|
-
│ │ APPLICATION LAYER │ │
|
|
48
|
-
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
49
|
-
│ │ │Interface│ │ DTOs │ │Commands │ │Validators│ │ │
|
|
50
|
-
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
51
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
52
|
-
│ │ │
|
|
53
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
54
|
-
│ │ INFRASTRUCTURE LAYER │ │
|
|
55
|
-
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
56
|
-
│ │ │ EF Core │ │ Service │ │ Seed │ │ SignalR │ │ Email │ │ │
|
|
57
|
-
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
58
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
59
|
-
│ │ │
|
|
60
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
61
|
-
│ │ API LAYER │ │
|
|
62
|
-
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
63
|
-
│ │ │Controller│ │Authorize │ │ Swagger │ │Postman │ │ │
|
|
64
|
-
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
65
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
66
|
-
│ │ │
|
|
67
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
68
|
-
│ │ WEB LAYER │ │
|
|
69
|
-
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
70
|
-
│ │ │ Pages │ │ Hooks │ │ i18n │ │ API │ │ Routes │ │ │
|
|
71
|
-
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
|
72
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
73
|
-
│ │ │
|
|
74
|
-
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
|
75
|
-
│ │ INTEGRATIONS │ │
|
|
76
|
-
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
77
|
-
│ │ │NOTIFICATIONS│ │ WORKFLOWS │ │ AI │ │ │
|
|
78
|
-
│ │ │ In-App │ │ Emails │ │ Analysis │ │ │
|
|
79
|
-
│ │ │ Real-time │ │ Webhooks │ │ Generation │ │ │
|
|
80
|
-
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
81
|
-
│ └───────────────────────────────────────────────────────────────────────┘ │
|
|
82
|
-
│ │
|
|
83
|
-
└─────────────────────────────────────────────────────────────────────────────┘
|
|
30
|
+
DOMAIN → APPLICATION → INFRASTRUCTURE → API → WEB
|
|
31
|
+
+ NOTIFICATIONS + WORKFLOWS + AI
|
|
84
32
|
```
|
|
85
33
|
|
|
86
|
-
---
|
|
87
|
-
|
|
88
34
|
## WORKFLOW DE CREATION
|
|
89
35
|
|
|
90
|
-
### PHASE 1: ANALYSE
|
|
36
|
+
### PHASE 1: ANALYSE
|
|
91
37
|
|
|
92
38
|
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
□ Quelles regles metier ?
|
|
98
|
-
|
|
99
|
-
2. DEFINIR LES INTEGRATIONS
|
|
100
|
-
□ Notifications requises ?
|
|
101
|
-
→ Quand ? Pour qui ? Quel type ?
|
|
102
|
-
□ Emails automatiques ?
|
|
103
|
-
→ Quels triggers ? Quels templates ?
|
|
104
|
-
□ Assistance IA ?
|
|
105
|
-
→ Quel use case ? Quelle validation ?
|
|
106
|
-
|
|
107
|
-
3. DEFINIR L'UX
|
|
108
|
-
□ Affichage: Cards, Table, Kanban ?
|
|
109
|
-
□ Navigation: Ou dans l'arborescence ?
|
|
110
|
-
□ Permissions: Qui peut faire quoi ?
|
|
39
|
+
□ Module/fonctionnalite ?
|
|
40
|
+
□ Entites ? Operations CRUD ? Regles metier ?
|
|
41
|
+
□ Notifications requises ? Emails automatiques ? IA ?
|
|
42
|
+
□ Affichage: Cards/Table/Kanban ? Permissions ?
|
|
111
43
|
```
|
|
112
44
|
|
|
113
|
-
### PHASE 2:
|
|
45
|
+
### PHASE 2: DOMAIN
|
|
114
46
|
|
|
115
47
|
```csharp
|
|
116
|
-
//
|
|
117
|
-
// src/SmartStack.Domain/{Area}/{Entity}.cs
|
|
118
|
-
|
|
48
|
+
// {Entity}.cs - Factory Method + Behaviors
|
|
119
49
|
public class {Entity} : BaseEntity, IAuditableEntity
|
|
120
50
|
{
|
|
121
|
-
// Proprietes
|
|
122
51
|
public string Name { get; private set; }
|
|
123
|
-
public string? Description { get; private set; }
|
|
124
52
|
public {Entity}Status Status { get; private set; }
|
|
125
|
-
|
|
126
|
-
// Relations
|
|
127
|
-
public Guid CreatedById { get; private set; }
|
|
128
|
-
public User CreatedBy { get; private set; } = null!;
|
|
129
|
-
|
|
130
|
-
// Audit
|
|
131
|
-
public string? CreatedByName { get; set; }
|
|
132
|
-
public string? UpdatedByName { get; set; }
|
|
133
|
-
|
|
134
|
-
// Private constructor for EF Core
|
|
135
53
|
private {Entity}() { }
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
public
|
|
139
|
-
string name,
|
|
140
|
-
string? description,
|
|
141
|
-
Guid createdById)
|
|
142
|
-
{
|
|
143
|
-
if (string.IsNullOrWhiteSpace(name))
|
|
144
|
-
throw new DomainException("{Entity} name is required");
|
|
145
|
-
|
|
146
|
-
return new {Entity}
|
|
147
|
-
{
|
|
148
|
-
Id = Guid.NewGuid(),
|
|
149
|
-
Name = name,
|
|
150
|
-
Description = description,
|
|
151
|
-
Status = {Entity}Status.Active,
|
|
152
|
-
CreatedById = createdById,
|
|
153
|
-
CreatedAt = DateTime.UtcNow
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Behaviors
|
|
158
|
-
public void Update(string name, string? description)
|
|
159
|
-
{
|
|
160
|
-
Name = name ?? throw new DomainException("Name is required");
|
|
161
|
-
Description = description;
|
|
162
|
-
UpdatedAt = DateTime.UtcNow;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
public void Activate() => Status = {Entity}Status.Active;
|
|
166
|
-
public void Deactivate() => Status = {Entity}Status.Inactive;
|
|
54
|
+
public static {Entity} Create(string name, Guid createdById) =>
|
|
55
|
+
new { Id = Guid.NewGuid(), Name = name, Status = {Entity}Status.Active, ... };
|
|
56
|
+
public void Update(string name) => Name = name ?? throw new DomainException("Name required");
|
|
167
57
|
}
|
|
168
58
|
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
public enum {Entity}Status
|
|
173
|
-
{
|
|
174
|
-
Active = 0,
|
|
175
|
-
Inactive = 1,
|
|
176
|
-
Archived = 2
|
|
177
|
-
}
|
|
59
|
+
// {Entity}Status.cs
|
|
60
|
+
public enum {Entity}Status { Active = 0, Inactive = 1, Archived = 2 }
|
|
178
61
|
```
|
|
179
62
|
|
|
180
|
-
### PHASE 3:
|
|
63
|
+
### PHASE 3: APPLICATION
|
|
181
64
|
|
|
182
65
|
```csharp
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
public interface I{Entity}Service
|
|
187
|
-
{
|
|
188
|
-
Task<PagedResult<{Entity}Dto>> GetAllAsync(
|
|
189
|
-
int page, int pageSize, string? search, CancellationToken ct);
|
|
190
|
-
Task<{Entity}Dto?> GetByIdAsync(Guid id, CancellationToken ct);
|
|
191
|
-
Task<{Entity}Dto> CreateAsync(Create{Entity}Request request, CancellationToken ct);
|
|
192
|
-
Task<{Entity}Dto> UpdateAsync(Guid id, Update{Entity}Request request, CancellationToken ct);
|
|
193
|
-
Task DeleteAsync(Guid id, CancellationToken ct);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// 2. DTOs
|
|
197
|
-
// src/SmartStack.Application/{Area}/DTOs/{Entity}Dto.cs
|
|
198
|
-
|
|
199
|
-
public record {Entity}Dto(
|
|
200
|
-
Guid Id,
|
|
201
|
-
string Name,
|
|
202
|
-
string? Description,
|
|
203
|
-
string Status,
|
|
204
|
-
DateTime CreatedAt,
|
|
205
|
-
string? CreatedByName);
|
|
66
|
+
// I{Entity}Service.cs
|
|
67
|
+
Task<PagedResult<{Entity}Dto>> GetAllAsync(...);
|
|
68
|
+
Task<{Entity}Dto> CreateAsync(Create{Entity}Request request, CancellationToken ct);
|
|
206
69
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
70
|
+
// DTOs
|
|
71
|
+
public record {Entity}Dto(Guid Id, string Name, string Status, DateTime CreatedAt);
|
|
72
|
+
public record Create{Entity}Request(string Name, string? Description);
|
|
210
73
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
string? Description);
|
|
214
|
-
|
|
215
|
-
// 3. PERMISSIONS
|
|
216
|
-
// src/SmartStack.Application/Common/Authorization/Permissions.cs
|
|
217
|
-
|
|
218
|
-
public static class {Area}
|
|
219
|
-
{
|
|
220
|
-
public static class {Module}
|
|
221
|
-
{
|
|
222
|
-
public const string View = "{context}.{area}.{module}.read";
|
|
223
|
-
public const string Create = "{context}.{area}.{module}.create";
|
|
224
|
-
public const string Update = "{context}.{area}.{module}.update";
|
|
225
|
-
public const string Delete = "{context}.{area}.{module}.delete";
|
|
226
|
-
}
|
|
227
|
-
}
|
|
74
|
+
// Permissions.cs + PermissionConfiguration.cs (2 fichiers!)
|
|
75
|
+
public const string View = "{context}.{area}.{module}.read";
|
|
228
76
|
```
|
|
229
77
|
|
|
230
|
-
### PHASE 4:
|
|
78
|
+
### PHASE 4: INFRASTRUCTURE
|
|
231
79
|
|
|
232
80
|
```csharp
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
{
|
|
238
|
-
|
|
239
|
-
{
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
.IsRequired();
|
|
247
|
-
|
|
248
|
-
builder.Property(x => x.Description)
|
|
249
|
-
.HasMaxLength(2000);
|
|
250
|
-
|
|
251
|
-
builder.HasIndex(x => x.Name);
|
|
252
|
-
builder.HasIndex(x => x.Status);
|
|
253
|
-
builder.HasIndex(x => x.CreatedById);
|
|
254
|
-
|
|
255
|
-
// Navigation
|
|
256
|
-
builder.HasOne(x => x.CreatedBy)
|
|
257
|
-
.WithMany()
|
|
258
|
-
.HasForeignKey(x => x.CreatedById)
|
|
259
|
-
.OnDelete(DeleteBehavior.Restrict);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// 2. SERVICE IMPLEMENTATION
|
|
264
|
-
// src/SmartStack.Infrastructure/Services/{Area}/{Entity}Service.cs
|
|
265
|
-
|
|
266
|
-
public class {Entity}Service : I{Entity}Service
|
|
267
|
-
{
|
|
268
|
-
private readonly IApplicationDbContext _context;
|
|
269
|
-
private readonly ICurrentUserService _currentUser;
|
|
270
|
-
private readonly INotificationService _notificationService;
|
|
271
|
-
private readonly ILogger<{Entity}Service> _logger;
|
|
272
|
-
|
|
273
|
-
public {Entity}Service(
|
|
274
|
-
IApplicationDbContext context,
|
|
275
|
-
ICurrentUserService currentUser,
|
|
276
|
-
INotificationService notificationService,
|
|
277
|
-
ILogger<{Entity}Service> logger)
|
|
278
|
-
{
|
|
279
|
-
_context = context;
|
|
280
|
-
_currentUser = currentUser;
|
|
281
|
-
_notificationService = notificationService;
|
|
282
|
-
_logger = logger;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
public async Task<{Entity}Dto> CreateAsync(
|
|
286
|
-
Create{Entity}Request request,
|
|
287
|
-
CancellationToken ct)
|
|
288
|
-
{
|
|
289
|
-
var entity = {Entity}.Create(
|
|
290
|
-
request.Name,
|
|
291
|
-
request.Description,
|
|
292
|
-
_currentUser.Id);
|
|
293
|
-
|
|
294
|
-
entity.CreatedByName = _currentUser.DisplayName;
|
|
295
|
-
|
|
296
|
-
_context.{Entity}s.Add(entity);
|
|
297
|
-
await _context.SaveChangesAsync(ct);
|
|
298
|
-
|
|
299
|
-
// NOTIFICATION
|
|
300
|
-
await _notificationService.SendNotificationAsync(
|
|
301
|
-
_currentUser.Id,
|
|
302
|
-
NotificationType.{Entity}Created,
|
|
303
|
-
"{Entity} creee",
|
|
304
|
-
$"{entity.Name} a ete cree avec succes",
|
|
305
|
-
relatedEntityType: nameof({Entity}),
|
|
306
|
-
relatedEntityId: entity.Id,
|
|
307
|
-
actionUrl: $"/{area}/{module}/{entity.Id}",
|
|
308
|
-
cancellationToken: ct);
|
|
309
|
-
|
|
310
|
-
_logger.LogInformation(
|
|
311
|
-
"User {UserId} created {EntityType} {EntityId}: {EntityName}",
|
|
312
|
-
_currentUser.Id, nameof({Entity}), entity.Id, entity.Name);
|
|
313
|
-
|
|
314
|
-
return MapToDto(entity);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// ... autres methodes CRUD avec notifications
|
|
81
|
+
// {Entity}Configuration.cs
|
|
82
|
+
builder.ToTable("{Entity}s", "{schema}");
|
|
83
|
+
builder.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
|
84
|
+
|
|
85
|
+
// {Entity}Service.cs avec Notifications
|
|
86
|
+
public async Task<{Entity}Dto> CreateAsync(...) {
|
|
87
|
+
var entity = {Entity}.Create(request.Name, _currentUser.Id);
|
|
88
|
+
_context.{Entity}s.Add(entity);
|
|
89
|
+
await _context.SaveChangesAsync(ct);
|
|
90
|
+
await _notificationService.SendNotificationAsync(_currentUser.Id,
|
|
91
|
+
NotificationType.{Entity}Created, "Cree", $"{entity.Name} cree",
|
|
92
|
+
relatedEntityType: nameof({Entity}), relatedEntityId: entity.Id, ct);
|
|
93
|
+
return MapToDto(entity);
|
|
318
94
|
}
|
|
319
95
|
|
|
320
|
-
//
|
|
321
|
-
// Ajouter dans DependencyInjection.cs:
|
|
96
|
+
// DependencyInjection.cs
|
|
322
97
|
services.AddScoped<I{Entity}Service, {Entity}Service>();
|
|
323
98
|
```
|
|
324
99
|
|
|
325
|
-
### PHASE 5:
|
|
100
|
+
### PHASE 5: API
|
|
326
101
|
|
|
327
102
|
```csharp
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
[ApiController]
|
|
331
|
-
[Route("api/{area}/{module}")]
|
|
332
|
-
[Authorize]
|
|
103
|
+
[ApiController][Route("api/{area}/{module}")][Authorize]
|
|
333
104
|
public class {Entity}Controller : ControllerBase
|
|
334
105
|
{
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
{
|
|
342
|
-
_service = service;
|
|
343
|
-
_logger = logger;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
[HttpGet]
|
|
347
|
-
[RequirePermission(Permissions.{Area}.{Module}.View)]
|
|
348
|
-
[ProducesResponseType(typeof(PagedResult<{Entity}Dto>), StatusCodes.Status200OK)]
|
|
349
|
-
public async Task<ActionResult<PagedResult<{Entity}Dto>>> GetAll(
|
|
350
|
-
[FromQuery] int page = 1,
|
|
351
|
-
[FromQuery] int pageSize = 20,
|
|
352
|
-
[FromQuery] string? search = null,
|
|
353
|
-
CancellationToken ct = default)
|
|
354
|
-
{
|
|
355
|
-
var result = await _service.GetAllAsync(page, pageSize, search, ct);
|
|
356
|
-
return Ok(result);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
[HttpGet("{id:guid}")]
|
|
360
|
-
[RequirePermission(Permissions.{Area}.{Module}.View)]
|
|
361
|
-
[ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status200OK)]
|
|
362
|
-
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
363
|
-
public async Task<ActionResult<{Entity}Dto>> GetById(Guid id, CancellationToken ct)
|
|
364
|
-
{
|
|
365
|
-
var result = await _service.GetByIdAsync(id, ct);
|
|
366
|
-
return result is null ? NotFound() : Ok(result);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
[HttpPost]
|
|
370
|
-
[RequirePermission(Permissions.{Area}.{Module}.Create)]
|
|
371
|
-
[ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status201Created)]
|
|
372
|
-
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
373
|
-
public async Task<ActionResult<{Entity}Dto>> Create(
|
|
374
|
-
[FromBody] Create{Entity}Request request,
|
|
375
|
-
CancellationToken ct)
|
|
376
|
-
{
|
|
377
|
-
var result = await _service.CreateAsync(request, ct);
|
|
378
|
-
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
[HttpPut("{id:guid}")]
|
|
382
|
-
[RequirePermission(Permissions.{Area}.{Module}.Update)]
|
|
383
|
-
[ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status200OK)]
|
|
384
|
-
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
385
|
-
public async Task<ActionResult<{Entity}Dto>> Update(
|
|
386
|
-
Guid id,
|
|
387
|
-
[FromBody] Update{Entity}Request request,
|
|
388
|
-
CancellationToken ct)
|
|
389
|
-
{
|
|
390
|
-
var result = await _service.UpdateAsync(id, request, ct);
|
|
391
|
-
return Ok(result);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
[HttpDelete("{id:guid}")]
|
|
395
|
-
[RequirePermission(Permissions.{Area}.{Module}.Delete)]
|
|
396
|
-
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
|
397
|
-
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
398
|
-
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
|
399
|
-
{
|
|
400
|
-
await _service.DeleteAsync(id, ct);
|
|
401
|
-
return NoContent();
|
|
402
|
-
}
|
|
106
|
+
[HttpGet][RequirePermission(Permissions.{Area}.{Module}.View)]
|
|
107
|
+
[ProducesResponseType(typeof(PagedResult<{Entity}Dto>), 200)]
|
|
108
|
+
public async Task<ActionResult<PagedResult<{Entity}Dto>>> GetAll(...);
|
|
109
|
+
|
|
110
|
+
[HttpPost][RequirePermission(Permissions.{Area}.{Module}.Create)]
|
|
111
|
+
[ProducesResponseType(typeof({Entity}Dto), 201)]
|
|
112
|
+
public async Task<ActionResult<{Entity}Dto>> Create([FromBody] request, CancellationToken ct);
|
|
403
113
|
}
|
|
404
114
|
```
|
|
405
115
|
|
|
406
|
-
### PHASE 6:
|
|
116
|
+
### PHASE 6: FRONTEND
|
|
407
117
|
|
|
408
118
|
```typescript
|
|
409
|
-
//
|
|
410
|
-
// web/src/services/api/{module}Api.ts
|
|
411
|
-
|
|
412
|
-
import { apiClient } from './apiClient';
|
|
413
|
-
import type { PagedResult, {Entity}Dto, Create{Entity}Request, Update{Entity}Request } from '@/types';
|
|
414
|
-
|
|
119
|
+
// {module}Api.ts
|
|
415
120
|
export const {module}Api = {
|
|
416
|
-
getAll: (page
|
|
417
|
-
|
|
418
|
-
params: { page, pageSize, search }
|
|
419
|
-
}),
|
|
420
|
-
|
|
421
|
-
getById: (id: string) =>
|
|
422
|
-
apiClient.get<{Entity}Dto>(`/{area}/{module}/${id}`),
|
|
423
|
-
|
|
424
|
-
create: (data: Create{Entity}Request) =>
|
|
425
|
-
apiClient.post<{Entity}Dto>('/{area}/{module}', data),
|
|
426
|
-
|
|
427
|
-
update: (id: string, data: Update{Entity}Request) =>
|
|
428
|
-
apiClient.put<{Entity}Dto>(`/{area}/{module}/${id}`, data),
|
|
429
|
-
|
|
430
|
-
delete: (id: string) =>
|
|
431
|
-
apiClient.delete(`/{area}/{module}/${id}`),
|
|
121
|
+
getAll: (page, pageSize, search?) => apiClient.get<PagedResult<{Entity}Dto>>('/{area}/{module}', { params }),
|
|
122
|
+
create: (data) => apiClient.post<{Entity}Dto>('/{area}/{module}', data),
|
|
432
123
|
};
|
|
433
124
|
|
|
434
|
-
//
|
|
435
|
-
// web/src/hooks/use{Module}.ts
|
|
436
|
-
|
|
437
|
-
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
438
|
-
import { {module}Api } from '@/services/api/{module}Api';
|
|
439
|
-
import { useSignalR } from '@/hooks/useSignalR';
|
|
440
|
-
import { toast } from 'sonner';
|
|
441
|
-
|
|
125
|
+
// use{Module}.ts avec SignalR
|
|
442
126
|
export function use{Module}() {
|
|
443
127
|
const queryClient = useQueryClient();
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
queryClient.invalidateQueries(['{module}']);
|
|
450
|
-
toast.info(notification.title, {
|
|
451
|
-
description: notification.message,
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
const {module}Query = useQuery({
|
|
458
|
-
queryKey: ['{module}'],
|
|
459
|
-
queryFn: () => {module}Api.getAll(),
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
const createMutation = useMutation({
|
|
463
|
-
mutationFn: {module}Api.create,
|
|
464
|
-
onSuccess: () => {
|
|
465
|
-
queryClient.invalidateQueries(['{module}']);
|
|
466
|
-
toast.success('{Entity} creee');
|
|
467
|
-
},
|
|
468
|
-
onError: (error) => {
|
|
469
|
-
toast.error('Erreur lors de la creation');
|
|
470
|
-
},
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
const updateMutation = useMutation({
|
|
474
|
-
mutationFn: ({ id, data }: { id: string; data: Update{Entity}Request }) =>
|
|
475
|
-
{module}Api.update(id, data),
|
|
476
|
-
onSuccess: () => {
|
|
477
|
-
queryClient.invalidateQueries(['{module}']);
|
|
478
|
-
toast.success('{Entity} mise a jour');
|
|
479
|
-
},
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
const deleteMutation = useMutation({
|
|
483
|
-
mutationFn: {module}Api.delete,
|
|
484
|
-
onSuccess: () => {
|
|
485
|
-
queryClient.invalidateQueries(['{module}']);
|
|
486
|
-
toast.success('{Entity} supprimee');
|
|
487
|
-
},
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
return {
|
|
491
|
-
{module}: {module}Query.data?.items ?? [],
|
|
492
|
-
isLoading: {module}Query.isLoading,
|
|
493
|
-
error: {module}Query.error,
|
|
494
|
-
create: createMutation.mutate,
|
|
495
|
-
update: updateMutation.mutate,
|
|
496
|
-
delete: deleteMutation.mutate,
|
|
497
|
-
isCreating: createMutation.isPending,
|
|
498
|
-
isUpdating: updateMutation.isPending,
|
|
499
|
-
isDeleting: deleteMutation.isPending,
|
|
500
|
-
};
|
|
128
|
+
useSignalR({ onNotification: (n) => {
|
|
129
|
+
if (n.relatedEntityType === '{Entity}') queryClient.invalidateQueries(['{module}']);
|
|
130
|
+
}});
|
|
131
|
+
const createMutation = useMutation({ mutationFn: {module}Api.create, onSuccess: () => ... });
|
|
132
|
+
return { create: createMutation.mutate, ... };
|
|
501
133
|
}
|
|
502
134
|
|
|
503
|
-
//
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
import { useState } from 'react';
|
|
507
|
-
import { Plus, Search } from 'lucide-react';
|
|
508
|
-
import { use{Module} } from '@/hooks/use{Module}';
|
|
509
|
-
import { EntityCard } from '@/components/ui/EntityCard';
|
|
510
|
-
import { {Module}Form } from './components/{Module}Form';
|
|
511
|
-
import { useTranslation } from 'react-i18next';
|
|
512
|
-
|
|
513
|
-
export function {Module}Page() {
|
|
514
|
-
const { t } = useTranslation('{module}');
|
|
515
|
-
const { {module}, isLoading, create, delete: remove } = use{Module}();
|
|
516
|
-
const [showForm, setShowForm] = useState(false);
|
|
517
|
-
const [search, setSearch] = useState('');
|
|
518
|
-
|
|
519
|
-
const filtered = {module}.filter(item =>
|
|
520
|
-
item.name.toLowerCase().includes(search.toLowerCase())
|
|
521
|
-
);
|
|
522
|
-
|
|
523
|
-
return (
|
|
524
|
-
<div className="space-y-6">
|
|
525
|
-
{/* Header */}
|
|
526
|
-
<div className="flex items-center justify-between">
|
|
527
|
-
<div>
|
|
528
|
-
<h1 className="text-2xl font-bold">{t('title')}</h1>
|
|
529
|
-
<p className="text-[var(--text-secondary)]">{t('subtitle')}</p>
|
|
530
|
-
</div>
|
|
531
|
-
<button
|
|
532
|
-
onClick={() => setShowForm(true)}
|
|
533
|
-
className="inline-flex items-center gap-2 px-4 py-2 bg-[var(--color-accent-600)] text-white rounded-lg"
|
|
534
|
-
>
|
|
535
|
-
<Plus className="w-4 h-4" />
|
|
536
|
-
{t('create')}
|
|
537
|
-
</button>
|
|
538
|
-
</div>
|
|
539
|
-
|
|
540
|
-
{/* Search */}
|
|
541
|
-
<div className="relative">
|
|
542
|
-
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--text-tertiary)]" />
|
|
543
|
-
<input
|
|
544
|
-
type="text"
|
|
545
|
-
placeholder={t('search')}
|
|
546
|
-
value={search}
|
|
547
|
-
onChange={(e) => setSearch(e.target.value)}
|
|
548
|
-
className="w-full pl-10 pr-4 py-2 border rounded-lg"
|
|
549
|
-
/>
|
|
550
|
-
</div>
|
|
551
|
-
|
|
552
|
-
{/* Grid */}
|
|
553
|
-
{isLoading ? (
|
|
554
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
555
|
-
{Array.from({ length: 6 }).map((_, i) => (
|
|
556
|
-
<div key={i} className="h-48 bg-[var(--bg-secondary)] animate-pulse rounded-lg" />
|
|
557
|
-
))}
|
|
558
|
-
</div>
|
|
559
|
-
) : filtered.length === 0 ? (
|
|
560
|
-
<div className="text-center py-12">
|
|
561
|
-
<p className="text-[var(--text-secondary)]">{t('empty')}</p>
|
|
562
|
-
</div>
|
|
563
|
-
) : (
|
|
564
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
565
|
-
{filtered.map((item) => (
|
|
566
|
-
<EntityCard
|
|
567
|
-
key={item.id}
|
|
568
|
-
avatar={{ letter: item.name[0], color: 'var(--color-accent-500)' }}
|
|
569
|
-
title={item.name}
|
|
570
|
-
subtitle={item.status}
|
|
571
|
-
description={item.description}
|
|
572
|
-
actions={[
|
|
573
|
-
{ label: t('view'), onClick: () => navigate(`/${area}/{module}/${item.id}`), variant: 'primary' },
|
|
574
|
-
{ label: t('delete'), onClick: () => remove(item.id), variant: 'ghost' },
|
|
575
|
-
]}
|
|
576
|
-
/>
|
|
577
|
-
))}
|
|
578
|
-
</div>
|
|
579
|
-
)}
|
|
580
|
-
|
|
581
|
-
{/* Form Modal */}
|
|
582
|
-
{showForm && (
|
|
583
|
-
<{Module}Form
|
|
584
|
-
onSubmit={(data) => {
|
|
585
|
-
create(data);
|
|
586
|
-
setShowForm(false);
|
|
587
|
-
}}
|
|
588
|
-
onClose={() => setShowForm(false)}
|
|
589
|
-
/>
|
|
590
|
-
)}
|
|
591
|
-
</div>
|
|
592
|
-
);
|
|
593
|
-
}
|
|
135
|
+
// {Module}Page.tsx avec EntityCard
|
|
136
|
+
<EntityCard title={item.name} actions={[{ label: 'Voir', onClick: () => navigate(...) }]} />
|
|
594
137
|
|
|
595
|
-
//
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
{
|
|
599
|
-
"title": "{Module}",
|
|
600
|
-
"subtitle": "Gerez vos {module}s",
|
|
601
|
-
"create": "Creer",
|
|
602
|
-
"search": "Rechercher...",
|
|
603
|
-
"empty": "Aucun {module} trouve",
|
|
604
|
-
"view": "Voir",
|
|
605
|
-
"edit": "Modifier",
|
|
606
|
-
"delete": "Supprimer",
|
|
607
|
-
"form": {
|
|
608
|
-
"name": "Nom",
|
|
609
|
-
"namePlaceholder": "Entrez le nom",
|
|
610
|
-
"description": "Description",
|
|
611
|
-
"descriptionPlaceholder": "Entrez une description",
|
|
612
|
-
"submit": "Enregistrer",
|
|
613
|
-
"cancel": "Annuler"
|
|
614
|
-
},
|
|
615
|
-
"notifications": {
|
|
616
|
-
"created": "{Module} creee avec succes",
|
|
617
|
-
"updated": "{Module} mise a jour",
|
|
618
|
-
"deleted": "{Module} supprimee"
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// 5. ROUTES
|
|
623
|
-
// App.tsx - Ajouter:
|
|
624
|
-
<Route path="{area}">
|
|
625
|
-
<Route index element={<Navigate to="{module}" replace />} />
|
|
626
|
-
<Route path="{module}">
|
|
627
|
-
<Route index element={<{Module}Page />} />
|
|
628
|
-
<Route path=":id" element={<{Module}DetailPage />} />
|
|
629
|
-
</Route>
|
|
630
|
-
</Route>
|
|
138
|
+
// Routes (nested obligatoire)
|
|
139
|
+
<Route path="{area}"><Route path="{module}"><Route index element={<{Module}Page />} /></Route></Route>
|
|
631
140
|
```
|
|
632
141
|
|
|
633
|
-
### PHASE 7: INTEGRATIONS
|
|
634
|
-
|
|
635
|
-
#### 7.1 Notifications
|
|
142
|
+
### PHASE 7: INTEGRATIONS
|
|
636
143
|
|
|
144
|
+
#### Notifications
|
|
637
145
|
```csharp
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
// Creation
|
|
641
|
-
await _notificationService.SendNotificationAsync(
|
|
642
|
-
_currentUser.Id,
|
|
643
|
-
NotificationType.{Entity}Created,
|
|
644
|
-
t["notifications.created.title"],
|
|
645
|
-
t["notifications.created.message", entity.Name],
|
|
646
|
-
relatedEntityType: nameof({Entity}),
|
|
647
|
-
relatedEntityId: entity.Id,
|
|
648
|
-
actionUrl: $"/{area}/{module}/{entity.Id}");
|
|
649
|
-
|
|
650
|
-
// Si assignation
|
|
651
|
-
if (entity.AssignedToId.HasValue)
|
|
652
|
-
{
|
|
653
|
-
await _notificationService.SendNotificationAsync(
|
|
654
|
-
entity.AssignedToId.Value,
|
|
655
|
-
NotificationType.{Entity}Assigned,
|
|
656
|
-
"{Entity} assignee",
|
|
657
|
-
$"La {entity.Name} vous a ete assignee",
|
|
658
|
-
relatedEntityType: nameof({Entity}),
|
|
659
|
-
relatedEntityId: entity.Id,
|
|
660
|
-
actionUrl: $"/{area}/{module}/{entity.Id}");
|
|
661
|
-
}
|
|
146
|
+
await _notificationService.SendNotificationAsync(userId, NotificationType.{Entity}Created,
|
|
147
|
+
title, message, relatedEntityType: nameof({Entity}), relatedEntityId: entity.Id, actionUrl);
|
|
662
148
|
```
|
|
663
149
|
|
|
664
|
-
####
|
|
665
|
-
|
|
150
|
+
#### Workflow Email
|
|
666
151
|
```csharp
|
|
667
|
-
// 1.
|
|
668
|
-
//
|
|
669
|
-
|
|
670
|
-
{
|
|
671
|
-
Id = Guid.Parse("NEW-TRIGGER-GUID"),
|
|
672
|
-
Code = "{entity}.created",
|
|
673
|
-
Name = "{Entity} Created",
|
|
674
|
-
AvailableVariablesJson = "[{\"Name\":\"entityId\"},{\"Name\":\"entityName\"},{\"Name\":\"creatorEmail\"}]",
|
|
675
|
-
IsActive = true,
|
|
676
|
-
CreatedAt = seedDate
|
|
677
|
-
},
|
|
678
|
-
|
|
679
|
-
// 2. Creer le workflow
|
|
680
|
-
// Dans WorkflowConfiguration.cs:
|
|
681
|
-
new
|
|
682
|
-
{
|
|
683
|
-
Id = Guid.Parse("NEW-WORKFLOW-GUID"),
|
|
684
|
-
Code = "{entity}-created-notification",
|
|
685
|
-
Name = "{Entity} Created Notification",
|
|
686
|
-
TriggerId = Guid.Parse("TRIGGER-GUID"),
|
|
687
|
-
IsActive = true,
|
|
688
|
-
IsSystem = true,
|
|
689
|
-
Priority = 10,
|
|
690
|
-
CreatedAt = seedDate
|
|
691
|
-
},
|
|
692
|
-
|
|
693
|
-
// 3. Declencher dans le service
|
|
694
|
-
await _workflowService.TriggerAsync(
|
|
695
|
-
"{entity}.created",
|
|
696
|
-
new Dictionary<string, object>
|
|
697
|
-
{
|
|
698
|
-
["entityId"] = entity.Id,
|
|
699
|
-
["entityName"] = entity.Name,
|
|
700
|
-
["creatorEmail"] = _currentUser.Email
|
|
701
|
-
});
|
|
152
|
+
// 1. WorkflowTriggerConfiguration.cs: new { Code = "{entity}.created", ... }
|
|
153
|
+
// 2. WorkflowConfiguration.cs: new { TriggerId = ..., ... }
|
|
154
|
+
// 3. Service:
|
|
155
|
+
await _workflowService.TriggerAsync("{entity}.created", new Dictionary<string, object>{...});
|
|
702
156
|
```
|
|
703
157
|
|
|
704
|
-
####
|
|
705
|
-
|
|
158
|
+
#### AI
|
|
706
159
|
```csharp
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
{
|
|
710
|
-
var entity = await _context.{Entity}s.FindAsync(entityId);
|
|
711
|
-
if (entity == null) return null;
|
|
712
|
-
|
|
713
|
-
var result = await _aiCompletionService
|
|
714
|
-
.ExecutePromptByCodeWithValidationAsync<{Entity}AnalysisResult>(
|
|
715
|
-
"{entity}-analyzer",
|
|
716
|
-
new Dictionary<string, object>
|
|
717
|
-
{
|
|
718
|
-
["entityName"] = entity.Name,
|
|
719
|
-
["entityDescription"] = entity.Description ?? ""
|
|
720
|
-
},
|
|
721
|
-
cancellationToken: ct);
|
|
722
|
-
|
|
723
|
-
if (result.Success && result.IsValid)
|
|
724
|
-
{
|
|
725
|
-
_logger.LogInformation(
|
|
726
|
-
"{Entity} {EntityId} analyzed: {Category}",
|
|
727
|
-
entityId, result.Data.Category);
|
|
728
|
-
return result.Data;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
return null;
|
|
732
|
-
}
|
|
160
|
+
var result = await _aiCompletionService.ExecutePromptByCodeWithValidationAsync<{Entity}AnalysisResult>(
|
|
161
|
+
"{entity}-analyzer", new Dictionary<string, object>{ ["name"] = entity.Name }, ct);
|
|
733
162
|
```
|
|
734
163
|
|
|
735
|
-
---
|
|
736
|
-
|
|
737
164
|
## CHECKLIST COMPLETE
|
|
738
165
|
|
|
739
|
-
###
|
|
740
|
-
```
|
|
741
|
-
□ Entite {Entity}.cs creee
|
|
742
|
-
□ Factory Method Create() implemente
|
|
743
|
-
□ Behaviors (Update, Activate, etc.)
|
|
744
|
-
□ Enum {Entity}Status.cs
|
|
745
|
-
□ IAuditableEntity implemente
|
|
746
|
-
```
|
|
747
|
-
|
|
748
|
-
### Application Layer
|
|
749
|
-
```
|
|
750
|
-
□ Interface I{Entity}Service.cs
|
|
751
|
-
□ DTOs: {Entity}Dto, Create/Update Requests
|
|
752
|
-
□ Permissions ajoutees dans Permissions.cs
|
|
753
|
-
□ Permissions ajoutees dans PermissionConfiguration.cs
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
### Infrastructure Layer
|
|
166
|
+
### Backend
|
|
757
167
|
```
|
|
758
|
-
□
|
|
759
|
-
□
|
|
760
|
-
□
|
|
761
|
-
□
|
|
762
|
-
□ Migration EF Core creee
|
|
168
|
+
□ Domain: Entity + Factory + Behaviors + Enum + IAuditableEntity
|
|
169
|
+
□ Application: Interface + DTOs + Permissions (2 fichiers!)
|
|
170
|
+
□ Infrastructure: EF Config + Service + DI + DbSet + Migration
|
|
171
|
+
□ API: Controller + Authorize + RequirePermission + ProducesResponseType
|
|
763
172
|
```
|
|
764
173
|
|
|
765
|
-
###
|
|
174
|
+
### Frontend
|
|
766
175
|
```
|
|
767
|
-
□ {
|
|
768
|
-
□
|
|
769
|
-
□
|
|
770
|
-
□ Logging dans les actions
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
### Web Layer
|
|
774
|
-
```
|
|
775
|
-
□ {module}Api.ts (service API)
|
|
776
|
-
□ use{Module}.ts (hook avec SignalR)
|
|
777
|
-
□ {Module}Page.tsx (page principale)
|
|
778
|
-
□ {Module}Form.tsx (formulaire)
|
|
779
|
-
□ {Module}DetailPage.tsx (detail)
|
|
780
|
-
□ i18n: fr/{module}.json + en/{module}.json
|
|
781
|
-
□ Routes dans App.tsx
|
|
176
|
+
□ {module}Api.ts + use{Module}.ts (avec SignalR) + {Module}Page.tsx + {Module}Form.tsx
|
|
177
|
+
□ i18n: fr, en, it, de
|
|
178
|
+
□ Routes nested dans App.tsx
|
|
782
179
|
```
|
|
783
180
|
|
|
784
181
|
### Integrations
|
|
785
182
|
```
|
|
786
|
-
□ Notifications:
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
□ useSignalR dans le hook
|
|
790
|
-
□ Workflows (si emails requis):
|
|
791
|
-
□ Trigger cree
|
|
792
|
-
□ Workflow cree
|
|
793
|
-
□ TriggerAsync dans le service
|
|
794
|
-
□ AI (si analyse requise):
|
|
795
|
-
□ Prompt cree
|
|
796
|
-
□ OutputSchema cree
|
|
797
|
-
□ ExecutePromptAsync dans le service
|
|
183
|
+
□ Notifications: NotificationType + SendNotificationAsync + useSignalR
|
|
184
|
+
□ Workflows: Trigger + Workflow + TriggerAsync
|
|
185
|
+
□ AI: Prompt + OutputSchema + ExecutePromptAsync
|
|
798
186
|
```
|
|
799
187
|
|
|
800
188
|
### Validation
|
|
801
189
|
```
|
|
802
|
-
□ dotnet build OK
|
|
803
|
-
□ npm run build OK
|
|
804
|
-
□ npm run lint OK
|
|
805
|
-
□ Tests unitaires
|
|
806
|
-
□ Test manuel E2E
|
|
190
|
+
□ dotnet build + npm run build + npm run lint OK
|
|
807
191
|
```
|
|
808
192
|
|
|
809
|
-
|
|
193
|
+
## SKILLS ORCHESTRES
|
|
810
194
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
|
816
|
-
|
|
817
|
-
| `/
|
|
818
|
-
| `/
|
|
819
|
-
| `/
|
|
820
|
-
| `/workflow` | 7.2 | Integration workflows/emails |
|
|
821
|
-
| `/ai-prompt` | 7.3 | Integration IA |
|
|
822
|
-
| `/ui-components` | 6 | Composants UI (EntityCard) |
|
|
823
|
-
| `/efcore:migration` | 4 | Migration EF Core |
|
|
824
|
-
|
|
825
|
-
---
|
|
195
|
+
| Skill | Phase |
|
|
196
|
+
|-------|-------|
|
|
197
|
+
| `/application` | 2-6 |
|
|
198
|
+
| `/controller` | 5 |
|
|
199
|
+
| `/notification` | 7.1 |
|
|
200
|
+
| `/workflow` | 7.2 |
|
|
201
|
+
| `/ai-prompt` | 7.3 |
|
|
202
|
+
| `/ui-components` | 6 |
|
|
203
|
+
| `/efcore:migration` | 4 |
|
|
826
204
|
|
|
827
205
|
## REGLES ABSOLUES
|
|
828
206
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
8. **TOUJOURS** documenter l'API avec ProducesResponseType
|
|
837
|
-
9. **JAMAIS** d'acces DB direct depuis le frontend
|
|
838
|
-
10. **JAMAIS** de permissions hardcodees en strings
|
|
207
|
+
| DO | DON'T |
|
|
208
|
+
|----|-------|
|
|
209
|
+
| Architecture en couches | Acces DB direct frontend |
|
|
210
|
+
| Factory Methods entites | Permissions strings hardcodees |
|
|
211
|
+
| Permissions 2 fichiers | Routes plates |
|
|
212
|
+
| Hooks SignalR | Skip i18n 4 langues |
|
|
213
|
+
| ProducesResponseType | Skip logging |
|