@atlashub/smartstack-cli 1.11.0 → 1.12.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.
Files changed (74) hide show
  1. package/.documentation/agents.html +7 -2
  2. package/.documentation/apex.html +7 -2
  3. package/.documentation/business-analyse.html +7 -2
  4. package/.documentation/cli-commands.html +871 -0
  5. package/.documentation/commands.html +7 -2
  6. package/.documentation/efcore.html +7 -2
  7. package/.documentation/gitflow.html +7 -2
  8. package/.documentation/hooks.html +7 -2
  9. package/.documentation/index.html +7 -2
  10. package/.documentation/init.html +7 -2
  11. package/.documentation/installation.html +7 -2
  12. package/.documentation/ralph-loop.html +7 -2
  13. package/.documentation/test-web.html +7 -2
  14. package/dist/index.js +1932 -336
  15. package/dist/index.js.map +1 -1
  16. package/package.json +8 -2
  17. package/templates/agents/efcore/squash.md +67 -31
  18. package/templates/agents/gitflow/finish.md +68 -56
  19. package/templates/commands/business-analyse/0-orchestrate.md +72 -556
  20. package/templates/commands/business-analyse/1-init.md +23 -193
  21. package/templates/commands/business-analyse/2-discover.md +85 -462
  22. package/templates/commands/business-analyse/3-analyse.md +40 -342
  23. package/templates/commands/business-analyse/4-specify.md +72 -537
  24. package/templates/commands/business-analyse/5-validate.md +43 -237
  25. package/templates/commands/business-analyse/6-handoff.md +93 -682
  26. package/templates/commands/business-analyse/7-doc-html.md +45 -544
  27. package/templates/commands/business-analyse/_shared.md +176 -0
  28. package/templates/commands/business-analyse/bug.md +50 -257
  29. package/templates/commands/business-analyse/change-request.md +59 -283
  30. package/templates/commands/business-analyse/hotfix.md +36 -120
  31. package/templates/commands/business-analyse.md +55 -574
  32. package/templates/commands/efcore/_shared.md +206 -0
  33. package/templates/commands/efcore/conflicts.md +39 -201
  34. package/templates/commands/efcore/db-deploy.md +28 -237
  35. package/templates/commands/efcore/db-reset.md +41 -390
  36. package/templates/commands/efcore/db-seed.md +44 -323
  37. package/templates/commands/efcore/db-status.md +31 -210
  38. package/templates/commands/efcore/migration.md +45 -368
  39. package/templates/commands/efcore/rebase-snapshot.md +38 -241
  40. package/templates/commands/efcore/scan.md +35 -204
  41. package/templates/commands/efcore/squash.md +158 -251
  42. package/templates/commands/efcore.md +49 -177
  43. package/templates/commands/gitflow/1-init.md +94 -1318
  44. package/templates/commands/gitflow/10-start.md +86 -990
  45. package/templates/commands/gitflow/11-finish.md +264 -454
  46. package/templates/commands/gitflow/12-cleanup.md +40 -213
  47. package/templates/commands/gitflow/2-status.md +51 -386
  48. package/templates/commands/gitflow/3-commit.md +108 -801
  49. package/templates/commands/gitflow/4-plan.md +42 -13
  50. package/templates/commands/gitflow/5-exec.md +60 -5
  51. package/templates/commands/gitflow/6-abort.md +54 -277
  52. package/templates/commands/gitflow/7-pull-request.md +74 -717
  53. package/templates/commands/gitflow/8-review.md +51 -178
  54. package/templates/commands/gitflow/9-merge.md +74 -404
  55. package/templates/commands/gitflow/_shared.md +196 -0
  56. package/templates/commands/quickstart.md +154 -0
  57. package/templates/commands/ralph-loop/ralph-loop.md +104 -2
  58. package/templates/hooks/hooks.json +13 -0
  59. package/templates/hooks/ralph-mcp-logger.sh +46 -0
  60. package/templates/hooks/ralph-session-end.sh +69 -0
  61. package/templates/ralph/README.md +91 -0
  62. package/templates/ralph/ralph.config.yaml +113 -0
  63. package/templates/scripts/setup-ralph-loop.sh +173 -0
  64. package/templates/skills/_shared.md +117 -0
  65. package/templates/skills/ai-prompt/SKILL.md +87 -654
  66. package/templates/skills/application/SKILL.md +76 -499
  67. package/templates/skills/controller/SKILL.md +38 -165
  68. package/templates/skills/documentation/SKILL.md +2 -1
  69. package/templates/skills/feature-full/SKILL.md +107 -732
  70. package/templates/skills/notification/SKILL.md +85 -474
  71. package/templates/skills/ui-components/SKILL.md +62 -762
  72. package/templates/skills/workflow/SKILL.md +85 -489
  73. package/templates/commands/gitflow/rescue.md +0 -867
  74. package/templates/skills/business-analyse/SKILL.md +0 -191
@@ -12,544 +12,155 @@ description: |
12
12
 
13
13
  # Skill Notification SmartStack
14
14
 
15
- > **Architecture:** Notifications = In-App (DB + SignalR) + Email (via Workflows)
16
- > Les notifications push sont prevues mais pas encore implementees.
15
+ > **Architecture:** In-App (DB + SignalR) + Email (via Workflows)
17
16
 
18
- ## QUAND CE SKILL S'ACTIVE
17
+ **Référence:** [_shared.md](../_shared.md) pour services communs
19
18
 
20
- Claude invoque automatiquement ce skill quand il detecte :
19
+ ## QUAND CE SKILL S'ACTIVE
21
20
 
22
21
  | Declencheur | Exemple |
23
22
  |-------------|---------|
24
23
  | Demande explicite | "Notifie l'utilisateur quand un ticket est cree" |
25
- | Mention notification | "Il faut alerter l'admin si..." |
26
24
  | Event-driven | "Quand le SLA expire, prevenir l'utilisateur" |
27
25
  | Real-time | "Afficher en temps reel les nouvelles notifications" |
28
- | Mots-cles | "notifier", "alerte", "notification", "SignalR", "real-time" |
29
-
30
- ---
26
+ | Mots-cles | "notifier", "alerte", "SignalR", "real-time" |
31
27
 
32
- ## ARCHITECTURE NOTIFICATIONS
28
+ ## FLOW
33
29
 
34
30
  ```
35
- ┌─────────────────────────────────────────────────────────────────────────────┐
36
- │ NOTIFICATION FLOW │
37
- ├─────────────────────────────────────────────────────────────────────────────┤
38
- │ │
39
- │ [EVENT] ─────┬──────────────────────────────────────────────────────────┐ │
40
- │ │ │ │
41
- │ ▼ │ │
42
- │ ┌─────────────────────────┐ │ │
43
- │ │ INotificationService │ │ │
44
- │ │ SendNotificationAsync │ │ │
45
- │ └────────────┬────────────┘ │ │
46
- │ │ │ │
47
- │ ┌───────┴───────┐ │ │
48
- │ ▼ ▼ │ │
49
- │ ┌─────────┐ ┌───────────────────┐ │ │
50
- │ │ DB │ │ SignalR Hub │ │ │
51
- │ │ (store) │ │ (real-time push) │ │ │
52
- │ └─────────┘ └─────────┬─────────┘ │ │
53
- │ │ │ │
54
- │ ▼ │ │
55
- │ ┌──────────────────┐ │ │
56
- │ │ Frontend Hook │ │ │
57
- │ │ useSignalR() │ │ │
58
- │ └─────────┬────────┘ │ │
59
- │ │ │ │
60
- │ ▼ │ │
61
- │ ┌──────────────────┐ │ │
62
- │ │ NotificationBell │ │ │
63
- │ │ (UI Component) │ │ │
64
- │ └──────────────────┘ │ │
65
- │ │ │
66
- │ [EMAIL] ── IWorkflowService.TriggerAsync() ── EmailTemplate ── SMTP │ │
67
- │ │ │
68
- └─────────────────────────────────────────────────────────────────────────────┘
31
+ EVENT → INotificationService.SendNotificationAsync()
32
+
33
+ DB (store) + SignalR Hub (real-time)
34
+
35
+ Frontend: useSignalR() → NotificationBell
69
36
  ```
70
37
 
71
- ---
72
-
73
38
  ## TYPES DE NOTIFICATION
74
39
 
75
- ### Enum NotificationType
76
-
77
- | Type | Declencheur | Usage |
78
- |------|-------------|-------|
79
- | `TicketCreated` | Nouveau ticket | Support |
80
- | `TicketAssigned` | Ticket assigne | Support |
81
- | `TicketStatusChanged` | Changement statut | Support |
82
- | `TicketCommentAdded` | Nouveau commentaire | Support |
83
- | `TicketResolved` | Ticket resolu | Support |
84
- | `RoleAssigned` | Role attribue | Admin |
85
- | `RoleRemoved` | Role retire | Admin |
86
- | `RolePermissionsChanged` | Permissions modifiees | Admin |
87
- | `SystemAnnouncement` | Annonce systeme | Global |
88
- | `AccountUpdated` | Compte modifie | User |
89
- | `SlaWarning` | SLA proche expiration | SLA |
90
- | `SlaResponseBreached` | SLA reponse depasse | SLA |
91
- | `SlaResolutionBreached` | SLA resolution depasse | SLA |
40
+ | Type | Usage |
41
+ |------|-------|
42
+ | `TicketCreated/Assigned/StatusChanged` | Support |
43
+ | `RoleAssigned/Removed/PermissionsChanged` | Admin |
44
+ | `SystemAnnouncement` | Global |
45
+ | `SlaWarning/ResponseBreached/ResolutionBreached` | SLA |
92
46
 
93
47
  ### Ajouter un Nouveau Type
94
-
95
48
  ```csharp
96
49
  // 1. Domain/Support/Enums/NotificationType.cs
97
- public enum NotificationType
98
- {
99
- // ... existants ...
100
-
101
- // Ajouter le nouveau type
102
- $NEW_TYPE = XX, // Increment depuis le dernier
103
- }
50
+ public enum NotificationType { ..., $NEW_TYPE = XX }
104
51
 
105
- // 2. Utilisation dans le service
106
- await _notificationService.SendNotificationAsync(
107
- userId,
108
- NotificationType.$NEW_TYPE,
109
- title,
110
- message,
111
- relatedEntityType: "EntityName",
112
- relatedEntityId: entityId,
113
- actionUrl: "/path/to/entity"
114
- );
52
+ // 2. Utilisation
53
+ await _notificationService.SendNotificationAsync(userId, NotificationType.$NEW_TYPE, title, message,
54
+ relatedEntityType: "Entity", relatedEntityId: id, actionUrl: "/path");
115
55
  ```
116
56
 
117
- ---
118
-
119
57
  ## WORKFLOW INTEGRATION
120
58
 
121
- ### ETAPE 1: Identifier le Besoin
122
-
123
- | Question | Reponse → Action |
124
- |----------|------------------|
125
- | L'utilisateur doit etre notifie en temps reel ? | → In-App + SignalR |
126
- | L'utilisateur doit recevoir un email ? | → Workflow + EmailTemplate |
127
- | L'action est liee a une entite ? | → Ajouter relatedEntityType/Id |
128
- | L'utilisateur peut naviguer vers l'entite ? | → Ajouter actionUrl |
129
-
130
- ### ETAPE 2: Injection du Service
59
+ ### 1. Identifier le Besoin
60
+ | Question | Action |
61
+ |----------|--------|
62
+ | Real-time ? | → In-App + SignalR |
63
+ | Email ? | → Workflow + EmailTemplate |
64
+ | Entite liee ? | → relatedEntityType/Id |
65
+ | Navigation ? | → actionUrl |
131
66
 
67
+ ### 2. Envoi de Notification
132
68
  ```csharp
133
- // Dans le Controller ou Service
134
- public class MyController : ControllerBase
135
- {
136
- private readonly INotificationService _notificationService;
137
- private readonly ILogger<MyController> _logger;
138
-
139
- public MyController(
140
- INotificationService notificationService,
141
- ILogger<MyController> logger)
142
- {
143
- _notificationService = notificationService;
144
- _logger = logger;
145
- }
146
- }
147
- ```
69
+ // Simple
70
+ await _notificationService.SendNotificationAsync(userId, NotificationType.TicketCreated,
71
+ "Nouveau ticket", $"Ticket #{ticket.Number} cree", ct);
148
72
 
149
- ### ETAPE 3: Envoi de Notification
73
+ // Avec entite liee
74
+ await _notificationService.SendNotificationAsync(userId, NotificationType.TicketAssigned,
75
+ "Ticket assigne", message,
76
+ relatedEntityType: "Ticket", relatedEntityId: ticket.Id,
77
+ actionUrl: $"/support/tickets/{ticket.Id}", ct);
150
78
 
151
- ```csharp
152
- // Notification simple
153
- await _notificationService.SendNotificationAsync(
154
- userId: targetUserId,
155
- type: NotificationType.TicketCreated,
156
- title: "Nouveau ticket",
157
- message: $"Le ticket #{ticket.Number} a ete cree",
158
- cancellationToken: ct
159
- );
79
+ // Multi-utilisateurs
80
+ await _notificationService.SendNotificationsAsync(userIds, type, title, message, ct);
160
81
 
161
- // Notification avec entite liee
162
- await _notificationService.SendNotificationAsync(
163
- userId: ticket.AssignedToId.Value,
164
- type: NotificationType.TicketAssigned,
165
- title: "Ticket assigne",
166
- message: $"Le ticket #{ticket.Number} vous a ete assigne",
167
- relatedEntityType: "Ticket",
168
- relatedEntityId: ticket.Id,
169
- actionUrl: $"/support/tickets/{ticket.Id}",
170
- cancellationToken: ct
171
- );
172
-
173
- // Notification a plusieurs utilisateurs
174
- await _notificationService.SendNotificationsAsync(
175
- userIds: teamUserIds,
176
- type: NotificationType.SystemAnnouncement,
177
- title: "Annonce equipe",
178
- message: "Reunion planifiee demain a 10h",
179
- cancellationToken: ct
180
- );
181
-
182
- // Notification a un role
183
- await _notificationService.SendNotificationToRoleAsync(
184
- roleName: "Admin",
185
- type: NotificationType.SystemAnnouncement,
186
- title: "Alerte systeme",
187
- message: "Maintenance planifiee ce soir",
188
- cancellationToken: ct
189
- );
82
+ // Par role
83
+ await _notificationService.SendNotificationToRoleAsync("Admin", type, title, message, ct);
190
84
  ```
191
85
 
192
- ### ETAPE 4: Preferences Utilisateur
193
-
86
+ ### 3. Preferences Utilisateur
194
87
  ```csharp
195
- // Verifier si l'utilisateur veut etre notifie
196
- var (shouldEmail, shouldInApp, shouldPush) =
197
- await _notificationService.ShouldNotifyAsync(userId, notificationType);
198
-
199
- if (shouldInApp)
200
- {
201
- await _notificationService.SendNotificationAsync(...);
202
- }
203
-
204
- if (shouldEmail)
205
- {
206
- // Declencher workflow email
207
- await _workflowService.TriggerAsync("ticket.created", new Dictionary<string, object>
208
- {
209
- ["ticketNumber"] = ticket.Number,
210
- ["ticketTitle"] = ticket.Title,
211
- ["userEmail"] = user.Email,
212
- ["userName"] = user.DisplayName
213
- });
214
- }
88
+ var (shouldEmail, shouldInApp, shouldPush) = await _notificationService.ShouldNotifyAsync(userId, type);
89
+ if (shouldInApp) await _notificationService.SendNotificationAsync(...);
90
+ if (shouldEmail) await _workflowService.TriggerAsync("ticket.created", variables);
215
91
  ```
216
92
 
217
- ---
218
-
219
- ## FRONTEND INTEGRATION
93
+ ## FRONTEND
220
94
 
221
95
  ### Hook useSignalR
222
-
223
96
  ```typescript
224
- // hooks/useSignalR.ts
225
- import { useSignalR } from '@/hooks/useSignalR';
226
-
227
- function MyComponent() {
228
- useSignalR({
229
- onNotification: (notification) => {
230
- // Notification recue en real-time
231
- console.log('New notification:', notification);
232
- toast.info(notification.title);
233
- },
234
- onUnreadCountUpdate: (count) => {
235
- // Mise a jour du badge
236
- setUnreadCount(count);
237
- },
238
- onPermissionsChanged: () => {
239
- // Permissions modifiees - recharger
240
- queryClient.invalidateQueries(['permissions']);
241
- },
242
- });
243
- }
97
+ useSignalR({
98
+ onNotification: (notification) => { toast.info(notification.title); },
99
+ onUnreadCountUpdate: (count) => { setUnreadCount(count); },
100
+ onPermissionsChanged: () => { queryClient.invalidateQueries(['permissions']); }
101
+ });
244
102
  ```
245
103
 
246
104
  ### API Notifications
247
-
248
105
  ```typescript
249
- // services/api/notificationsApi.ts
250
- import { apiClient } from './apiClient';
251
-
252
- export const notificationsApi = {
253
- // Recuperer les non-lues
254
- getUnread: (limit = 10) =>
255
- apiClient.get<NotificationDto[]>(`/notifications/unread?limit=${limit}`),
256
-
257
- // Recuperer avec pagination
258
- getAll: (page = 1, pageSize = 20, isRead?: boolean) =>
259
- apiClient.get<PagedResult<NotificationDto>>('/notifications', {
260
- params: { page, pageSize, isRead }
261
- }),
262
-
263
- // Compter les non-lues
264
- getUnreadCount: () =>
265
- apiClient.get<{ count: number }>('/notifications/unread/count'),
266
-
267
- // Marquer comme lue
268
- markAsRead: (id: string) =>
269
- apiClient.post(`/notifications/${id}/read`),
270
-
271
- // Marquer toutes comme lues
272
- markAllAsRead: () =>
273
- apiClient.post('/notifications/read-all'),
274
-
275
- // Supprimer
276
- delete: (id: string) =>
277
- apiClient.delete(`/notifications/${id}`),
278
-
279
- // Supprimer toutes
280
- deleteAll: () =>
281
- apiClient.delete('/notifications'),
282
- };
106
+ notificationsApi.getUnread(limit)
107
+ notificationsApi.getAll(page, pageSize, isRead?)
108
+ notificationsApi.getUnreadCount()
109
+ notificationsApi.markAsRead(id) / markAllAsRead()
110
+ notificationsApi.delete(id) / deleteAll()
283
111
  ```
284
112
 
285
- ### Composant NotificationBell
286
-
113
+ ### NotificationBell Component
287
114
  ```tsx
288
- // components/notifications/NotificationBell.tsx
289
- import { Bell } from 'lucide-react';
290
- import { useSignalR } from '@/hooks/useSignalR';
291
- import { notificationsApi } from '@/services/api/notificationsApi';
292
-
293
- export function NotificationBell() {
294
- const [notifications, setNotifications] = useState<NotificationDto[]>([]);
295
- const [unreadCount, setUnreadCount] = useState(0);
296
- const [isOpen, setIsOpen] = useState(false);
297
-
298
- // Real-time updates
299
- useSignalR({
300
- onNotification: (notification) => {
301
- setNotifications(prev => [notification, ...prev]);
302
- setUnreadCount(prev => prev + 1);
303
- },
304
- onUnreadCountUpdate: (count) => {
305
- setUnreadCount(count);
306
- },
307
- });
308
-
309
- // Load initial data
310
- useEffect(() => {
311
- loadNotifications();
312
- }, []);
313
-
314
- return (
315
- <div className="relative">
316
- <button onClick={() => setIsOpen(!isOpen)} className="relative p-2">
317
- <Bell className="w-5 h-5" />
318
- {unreadCount > 0 && (
319
- <span className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
320
- {unreadCount > 99 ? '99+' : unreadCount}
321
- </span>
322
- )}
323
- </button>
324
-
325
- {isOpen && (
326
- <NotificationDropdown
327
- notifications={notifications}
328
- onMarkAsRead={handleMarkAsRead}
329
- onMarkAllAsRead={handleMarkAllAsRead}
330
- onDelete={handleDelete}
331
- />
332
- )}
333
- </div>
334
- );
335
- }
115
+ <NotificationBell /> // Gere useSignalR, badge unreadCount, dropdown
336
116
  ```
337
117
 
338
- ---
339
-
340
- ## TEMPLATES
341
-
342
- ### Template Service (Backend)
118
+ ## PATTERNS AVANCES
343
119
 
120
+ ### SLA Warning (Job Hangfire)
344
121
  ```csharp
345
- // Services/{Module}/{Module}Service.cs
346
-
347
- public async Task<Result> Create{Entity}Async(Create{Entity}Command command, CancellationToken ct)
348
- {
349
- // ... creation de l'entite ...
350
-
351
- // Notification au createur (confirmation)
352
- await _notificationService.SendNotificationAsync(
353
- _currentUser.Id,
354
- NotificationType.{Entity}Created,
355
- "{Entity} creee",
356
- $"Votre {entity.Name} a ete creee avec succes",
357
- relatedEntityType: "{Entity}",
358
- relatedEntityId: entity.Id,
359
- actionUrl: $"/{module}/{entity.Id}",
360
- ct);
361
-
362
- // Notification aux responsables
363
- if (entity.AssignedToId.HasValue)
364
- {
365
- await _notificationService.SendNotificationAsync(
366
- entity.AssignedToId.Value,
367
- NotificationType.{Entity}Assigned,
368
- "{Entity} assignee",
369
- $"La {entity.Name} vous a ete assignee par {_currentUser.DisplayName}",
370
- relatedEntityType: "{Entity}",
371
- relatedEntityId: entity.Id,
372
- actionUrl: $"/{module}/{entity.Id}",
373
- ct);
374
- }
375
-
376
- return Result.Success();
122
+ var tickets = await _context.Tickets
123
+ .Where(t => t.SlaDeadline <= DateTime.UtcNow.AddMinutes(30) && !t.SlaWarningNotificationSent)
124
+ .ToListAsync();
125
+ foreach (var ticket in tickets) {
126
+ await _notificationService.SendNotificationAsync(..., NotificationType.SlaWarning, ...);
127
+ ticket.SlaWarningNotificationSent = true;
377
128
  }
378
129
  ```
379
130
 
380
- ### Template Controller (Backend)
381
-
131
+ ### Notification Groupee
382
132
  ```csharp
383
- // Controllers/{Area}/{Module}Controller.cs
384
-
385
- [HttpPost]
386
- [RequirePermission(Permissions.{Module}.Create)]
387
- [ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status201Created)]
388
- public async Task<ActionResult<{Entity}Dto>> Create(
389
- [FromBody] Create{Entity}Request request,
390
- CancellationToken ct)
391
- {
392
- var entity = await _service.CreateAsync(request, ct);
393
-
394
- // Notification automatique dans le service
395
- // OU notification ici si logique specifique au controller
396
-
397
- _logger.LogInformation(
398
- "User {UserId} created {EntityType} {EntityId}",
399
- _currentUser.Id, nameof({Entity}), entity.Id);
400
-
401
- return CreatedAtAction(
402
- nameof(GetById),
403
- new { id = entity.Id },
404
- entity);
405
- }
133
+ if (entities.Count == 1) await SendSingle(...);
134
+ else await SendBatch($"{entities.Count} nouveaux elements", ...);
406
135
  ```
407
136
 
408
- ### Template Hook (Frontend)
409
-
410
- ```typescript
411
- // hooks/use{Module}Notifications.ts
412
-
413
- import { useSignalR } from '@/hooks/useSignalR';
414
- import { useQueryClient } from '@tanstack/react-query';
415
- import { toast } from 'sonner';
416
-
417
- export function use{Module}Notifications() {
418
- const queryClient = useQueryClient();
419
-
420
- useSignalR({
421
- onNotification: (notification) => {
422
- // Filtrer les notifications de ce module
423
- if (notification.relatedEntityType === '{Entity}') {
424
- // Invalider les queries pour refresh
425
- queryClient.invalidateQueries(['{module}']);
137
+ ## CHECKLIST
426
138
 
427
- // Toast avec action
428
- toast.info(notification.title, {
429
- description: notification.message,
430
- action: notification.actionUrl ? {
431
- label: 'Voir',
432
- onClick: () => navigate(notification.actionUrl),
433
- } : undefined,
434
- });
435
- }
436
- },
437
- });
438
- }
439
139
  ```
440
-
441
- ---
442
-
443
- ## CHECKLIST INTEGRATION
444
-
140
+ □ Type notification identifie (existant ou nouveau enum)
141
+ □ INotificationService injecte
142
+ □ Envoi avec: userId, type, title, message, relatedEntityType?, relatedEntityId?, actionUrl?
143
+ Frontend: useSignalR configure, toast/UI sur reception
144
+ □ Si email: workflow configure
445
145
  ```
446
- □ Type de notification identifie (existant ou nouveau)
447
- □ Si nouveau type: ajoute a NotificationType enum
448
- □ Service injecte: INotificationService
449
- □ Notification envoyee avec tous les champs:
450
- □ userId (destinataire)
451
- □ type (NotificationType)
452
- □ title (titre court)
453
- □ message (description)
454
- □ relatedEntityType (optionnel)
455
- □ relatedEntityId (optionnel)
456
- □ actionUrl (optionnel)
457
- □ Logging ajoute (LogInformation)
458
- □ Frontend: useSignalR hook configure
459
- □ Frontend: toast ou UI update sur reception
460
- □ Si email requis: workflow configure
461
- ```
462
-
463
- ---
464
-
465
- ## PATTERNS AVANCES
466
-
467
- ### Pattern: Notification avec Delai (SLA)
468
-
469
- ```csharp
470
- // Pour les SLA warnings, utiliser un job Hangfire
471
- public class SlaNotificationJob
472
- {
473
- public async Task CheckSlaBreaches()
474
- {
475
- var tickets = await _context.Tickets
476
- .Where(t => t.SlaDeadline <= DateTime.UtcNow.AddMinutes(30))
477
- .Where(t => !t.SlaWarningNotificationSent)
478
- .ToListAsync();
479
-
480
- foreach (var ticket in tickets)
481
- {
482
- await _notificationService.SendNotificationAsync(
483
- ticket.AssignedToId ?? ticket.CreatedById,
484
- NotificationType.SlaWarning,
485
- "SLA proche expiration",
486
- $"Le ticket #{ticket.Number} expire dans 30 minutes",
487
- relatedEntityType: "Ticket",
488
- relatedEntityId: ticket.Id,
489
- actionUrl: $"/support/tickets/{ticket.Id}");
490
-
491
- ticket.SlaWarningNotificationSent = true;
492
- }
493
-
494
- await _context.SaveChangesAsync();
495
- }
496
- }
497
- ```
498
-
499
- ### Pattern: Notification Groupee
500
-
501
- ```csharp
502
- // Pour eviter le spam, grouper les notifications similaires
503
- public async Task SendBatchNotification(
504
- Guid userId,
505
- NotificationType type,
506
- List<EntitySummary> entities)
507
- {
508
- if (entities.Count == 1)
509
- {
510
- await _notificationService.SendNotificationAsync(
511
- userId, type,
512
- $"Nouveau {entities[0].Type}",
513
- entities[0].Description,
514
- relatedEntityType: entities[0].Type,
515
- relatedEntityId: entities[0].Id);
516
- }
517
- else
518
- {
519
- await _notificationService.SendNotificationAsync(
520
- userId, type,
521
- $"{entities.Count} nouveaux elements",
522
- $"Vous avez {entities.Count} nouveaux {entities[0].Type}s a traiter",
523
- actionUrl: "/dashboard/pending");
524
- }
525
- }
526
- ```
527
-
528
- ---
529
146
 
530
147
  ## REGLES ABSOLUES
531
148
 
532
- 1. **TOUJOURS** utiliser INotificationService (jamais acces DB direct)
533
- 2. **TOUJOURS** utiliser les types NotificationType enum
534
- 3. **TOUJOURS** inclure le context (relatedEntityType/Id) quand applicable
535
- 4. **TOUJOURS** ajouter actionUrl pour navigation
536
- 5. **TOUJOURS** logger les notifications envoyees
537
- 6. **TOUJOURS** respecter les preferences utilisateur
538
- 7. **JAMAIS** envoyer de notification sans destinataire valide
539
- 8. **JAMAIS** hardcoder les messages (utiliser i18n ou templates)
540
- 9. **JAMAIS** spam l'utilisateur (grouper les notifications similaires)
541
- 10. **JAMAIS** oublier le CancellationToken
542
-
543
- ---
149
+ | DO | DON'T |
150
+ |----|-------|
151
+ | INotificationService | Acces DB direct |
152
+ | NotificationType enum | Types hardcodes |
153
+ | relatedEntityType/Id + actionUrl | Notifications sans contexte |
154
+ | Respecter preferences utilisateur | Spam notifications |
155
+ | CancellationToken | Messages hardcodes (utiliser i18n) |
544
156
 
545
157
  ## FICHIERS CLES
546
158
 
547
159
  | Fichier | Role |
548
160
  |---------|------|
549
- | `Domain/Support/Notification.cs` | Entite notification |
550
- | `Domain/Support/Enums/NotificationType.cs` | Types de notification |
551
- | `Application/Common/Interfaces/INotificationService.cs` | Interface service |
552
- | `Infrastructure/Services/Support/NotificationService.cs` | Implementation |
161
+ | `Domain/Support/Notification.cs` | Entite |
162
+ | `Domain/Support/Enums/NotificationType.cs` | Types |
163
+ | `Application/Common/Interfaces/INotificationService.cs` | Interface |
553
164
  | `Infrastructure/Services/SignalR/NotificationHubService.cs` | Real-time |
554
165
  | `web/src/hooks/useSignalR.ts` | Hook frontend |
555
- | `web/src/components/notifications/NotificationBell.tsx` | UI Component |
166
+ | `web/src/components/notifications/NotificationBell.tsx` | UI |