feat: WebSocket temps réel + Finance Workflow + corrections

- Task #6: WebSocket /ws/dashboard + Kafka events (5 topics)
  * Backend: KafkaEventProducer, KafkaEventConsumer
  * Mobile: WebSocketService (reconnection, heartbeat, typed events)
  * DashboardBloc: Auto-refresh depuis WebSocket events

- Finance Workflow: approbations + budgets (backend + mobile)
  * Backend: entities, services, resources, migrations Flyway V6
  * Mobile: features finance_workflow complète avec BLoC

- Corrections DI: interfaces IRepository partout
  * IProfileRepository, IOrganizationRepository, IMembreRepository
  * GetIt configuré avec @injectable

- Spec-Kit: constitution + templates mis à jour
  * .specify/memory/constitution.md enrichie
  * Templates agent, plan, spec, tasks, checklist

- Nettoyage: fichiers temporaires supprimés

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 02:12:17 +00:00
parent bbc409de9d
commit e8ad874015
635 changed files with 58160 additions and 20674 deletions

View File

@@ -0,0 +1,70 @@
/// Model de données Conversation avec sérialisation JSON
library conversation_model;
import 'package:json_annotation/json_annotation.dart';
import '../../domain/entities/conversation.dart';
import '../../domain/entities/message.dart';
import 'message_model.dart';
part 'conversation_model.g.dart';
@JsonSerializable(explicitToJson: true)
class ConversationModel extends Conversation {
@JsonKey(
fromJson: _messageFromJson,
toJson: _messageToJson,
)
@override
final Message? lastMessage;
const ConversationModel({
required super.id,
required super.name,
super.description,
required super.type,
required super.participantIds,
super.organizationId,
this.lastMessage,
super.unreadCount,
super.isMuted,
super.isPinned,
super.isArchived,
required super.createdAt,
super.updatedAt,
super.avatarUrl,
super.metadata,
}) : super(lastMessage: lastMessage);
static Message? _messageFromJson(Map<String, dynamic>? json) =>
json == null ? null : MessageModel.fromJson(json);
static Map<String, dynamic>? _messageToJson(Message? message) =>
message == null ? null : MessageModel.fromEntity(message).toJson();
factory ConversationModel.fromJson(Map<String, dynamic> json) =>
_$ConversationModelFromJson(json);
Map<String, dynamic> toJson() => _$ConversationModelToJson(this);
factory ConversationModel.fromEntity(Conversation conversation) {
return ConversationModel(
id: conversation.id,
name: conversation.name,
description: conversation.description,
type: conversation.type,
participantIds: conversation.participantIds,
organizationId: conversation.organizationId,
lastMessage: conversation.lastMessage,
unreadCount: conversation.unreadCount,
isMuted: conversation.isMuted,
isPinned: conversation.isPinned,
isArchived: conversation.isArchived,
createdAt: conversation.createdAt,
updatedAt: conversation.updatedAt,
avatarUrl: conversation.avatarUrl,
metadata: conversation.metadata,
);
}
Conversation toEntity() => this;
}

View File

@@ -0,0 +1,83 @@
/// Model de données Message avec sérialisation JSON
library message_model;
import 'package:json_annotation/json_annotation.dart';
import '../../domain/entities/message.dart';
part 'message_model.g.dart';
@JsonSerializable(explicitToJson: true)
class MessageModel extends Message {
const MessageModel({
required super.id,
required super.conversationId,
required super.senderId,
required super.senderName,
super.senderAvatar,
required super.content,
required super.type,
required super.status,
super.priority,
required super.recipientIds,
super.recipientRoles,
super.organizationId,
required super.createdAt,
super.readAt,
super.metadata,
super.attachments,
super.isEdited,
super.editedAt,
super.isDeleted,
});
factory MessageModel.fromJson(Map<String, dynamic> json) =>
_$MessageModelFromJson(json);
Map<String, dynamic> toJson() => _$MessageModelToJson(this);
factory MessageModel.fromEntity(Message message) {
return MessageModel(
id: message.id,
conversationId: message.conversationId,
senderId: message.senderId,
senderName: message.senderName,
senderAvatar: message.senderAvatar,
content: message.content,
type: message.type,
status: message.status,
priority: message.priority,
recipientIds: message.recipientIds,
recipientRoles: message.recipientRoles,
organizationId: message.organizationId,
createdAt: message.createdAt,
readAt: message.readAt,
metadata: message.metadata,
attachments: message.attachments,
isEdited: message.isEdited,
editedAt: message.editedAt,
isDeleted: message.isDeleted,
);
}
Message toEntity() => Message(
id: id,
conversationId: conversationId,
senderId: senderId,
senderName: senderName,
senderAvatar: senderAvatar,
content: content,
type: type,
status: status,
priority: priority,
recipientIds: recipientIds,
recipientRoles: recipientRoles,
organizationId: organizationId,
createdAt: createdAt,
readAt: readAt,
metadata: metadata,
attachments: attachments,
isEdited: isEdited,
editedAt: editedAt,
isDeleted: isDeleted,
);
}