diff --git a/lib/core/di/cubit_registrations.dart b/lib/core/di/cubit_registrations.dart index d3886779..442ed86d 100644 --- a/lib/core/di/cubit_registrations.dart +++ b/lib/core/di/cubit_registrations.dart @@ -107,7 +107,9 @@ class CubitRegistrations { sl.registerFactory(() => FeedProfileCubit(profileRepository: sl())); // Chat related cubits - sl.registerFactory(() => ChatRoomCubit(cardsRepository: sl(), chatRepository: sl(), wsManager: sl())); + sl.registerFactory( + () => ChatRoomCubit(cardsRepository: sl(), chatRepository: sl(), wsManager: sl(), consultantRepository: sl()), + ); // Profile related cubits sl.registerFactory(() => ProfileCubit(profileRepository: sl(), chatRepository: sl(), consultantRepository: sl())); diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart index 27b81f50..a93e248f 100644 --- a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart @@ -17,6 +17,7 @@ import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_ import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; import 'package:eatvio_app/features/chat/utils/chat_counterpart_utils.dart'; +import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart'; import 'package:eatvio_app/features/my_finance/models/card_model.dart'; import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart'; import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; @@ -27,6 +28,7 @@ import 'package:uuid/uuid.dart'; class ChatRoomCubit extends Cubit { final ChatRepository chatRepository; final CardsRepository cardsRepository; + final ConsultantRepository consultantRepository; final WsManager wsManager; StreamSubscription? typingSubscription; @@ -39,7 +41,12 @@ class ChatRoomCubit extends Cubit { void Function(bool isOnline)? _presenceOnlineListener; var uuid = const Uuid(); - ChatRoomCubit({required this.chatRepository, required this.wsManager, required this.cardsRepository}) : super(const ChatRoomState.loading()); + ChatRoomCubit({ + required this.chatRepository, + required this.wsManager, + required this.cardsRepository, + required this.consultantRepository, + }) : super(const ChatRoomState.loading()); void setTimer() { _timer?.cancel(); @@ -91,6 +98,18 @@ class ChatRoomCubit extends Cubit { } } + Future checkAlfaScoringStatus(String slug) async { + try { + final status = await consultantRepository.getMyRequestStatus(slug); + if (status.legalEntity?.alfaScoringStatus.isComplete == true) { + _emitLoaded((current) => current.copyWith(isAlfaScoringComplete: true)); + } + } catch (e, st) { + await reportFrom(e, st, this); + log(e.toString(), stackTrace: st); + } + } + /// Инициализирует чат по [uuid] либо по [orderId] (resolve → conversation_uuid). /// /// При наличии [orderId] сначала вызывается @@ -137,8 +156,12 @@ class ChatRoomCubit extends Cubit { ); await _subscribeToPresenceChannel(chatId: chatRoom.id); - if (chosenPlan?.consultantId == chatRoom.users.sender.id) { + if (chosenPlan?.consultantId == wsManager.userId) { + final mySlug = chatRoom.users.sender.id == wsManager.userId + ? chatRoom.users.sender.slug + : chatRoom.users.receiver.slug; await checkCards(); + await checkAlfaScoringStatus(mySlug); } return resolvedUuid; } catch (e, st) { diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart index 36ca4f35..a76f4cc3 100644 --- a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart @@ -14,6 +14,7 @@ abstract class ChatRoomState with _$ChatRoomState { required ChatRoomModel chatRoom, required List chatMessages, required bool hasCard, + @Default(false) bool isAlfaScoringComplete, required bool isTyping, required bool isLoading, }) = ChatRoomStateLoaded; diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index 4ef7ba4e..80c9908c 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -190,7 +190,10 @@ class _ChatScreenState extends State { BannerType? bannerType; if (chosenPlan == null || chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateCanceledByTheInitiator) { bannerType = null; - } else if (amIConsultant && !state.hasCard && chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateInCoordination) { + } else if (amIConsultant && + !state.hasCard && + !state.isAlfaScoringComplete && + chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateInCoordination) { bannerType = BannerType.card; } else if (amIConsultant && chosenPlan.chosenPlanState == ChosenPlanStateEnum.stateInCoordination) { bannerType = BannerType.date; diff --git a/test/features/chat/presentation/bloc/chat_room_cubit/chat_room_state_test.dart b/test/features/chat/presentation/bloc/chat_room_cubit/chat_room_state_test.dart index 64534f01..726b7b6a 100644 --- a/test/features/chat/presentation/bloc/chat_room_cubit/chat_room_state_test.dart +++ b/test/features/chat/presentation/bloc/chat_room_cubit/chat_room_state_test.dart @@ -36,6 +36,7 @@ void main() { expect((state as ChatRoomStateLoaded).chatRoom, chatRoom); expect(state.chatMessages, isEmpty); expect(state.hasCard, isFalse); + expect(state.isAlfaScoringComplete, isFalse); expect(state.isTyping, isFalse); expect(state.isLoading, isFalse); }); diff --git a/test/features/chat/presentation/bloc/chat_room_cubit_test.dart b/test/features/chat/presentation/bloc/chat_room_cubit_test.dart index 38793443..41b8266c 100644 --- a/test/features/chat/presentation/bloc/chat_room_cubit_test.dart +++ b/test/features/chat/presentation/bloc/chat_room_cubit_test.dart @@ -9,6 +9,8 @@ import 'package:eatvio_app/features/chat/models/chat_user_model.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; +import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart'; +import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart'; import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart'; import 'package:eatvio_app/features/my_finance/models/card_model.dart'; import 'package:eatvio_app/features/chat/models/order_id_to_conversation_model.dart'; @@ -21,11 +23,13 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:dart_pusher_channels/dart_pusher_channels.dart'; +import '../../../../fixtures/fixture_reader.dart'; import 'chat_room_cubit_test.mocks.dart'; @GenerateNiceMocks([ MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec>(), MockSpec(), @@ -36,8 +40,17 @@ void main() { late ChatRoomCubit chatRoomCubit; late MockChatRepository mockChatRepository; late MockCardsRepository mockCardsRepository; + late MockConsultantRepository mockConsultantRepository; late MockWsManager mockWsManager; + ConsultationRequestModel requestWithScoring(String scoringStatus) { + final json = FixtureReader.readAsMap('consultant/consultation_request.json'); + final legal = Map.from(json['legal_entity_resource'] as Map); + legal['alfa_scoring_status'] = scoringStatus; + json['legal_entity_resource'] = legal; + return ConsultationRequestModel.fromJson(json); + } + const testChatRoom = chat_room.ChatRoomModel( id: 'chat-id', chatRoomSettings: ChatRoomSettingsModel(name: 'Test Room', state: 'active'), @@ -67,8 +80,14 @@ void main() { setUp(() { mockChatRepository = MockChatRepository(); mockCardsRepository = MockCardsRepository(); + mockConsultantRepository = MockConsultantRepository(); mockWsManager = MockWsManager(); - chatRoomCubit = ChatRoomCubit(chatRepository: mockChatRepository, wsManager: mockWsManager, cardsRepository: mockCardsRepository); + chatRoomCubit = ChatRoomCubit( + chatRepository: mockChatRepository, + wsManager: mockWsManager, + cardsRepository: mockCardsRepository, + consultantRepository: mockConsultantRepository, + ); }); tearDown(() async { @@ -405,7 +424,7 @@ void main() { await chatRoomCubit.initChat('uuid'); await Future.delayed(const Duration(milliseconds: 200)); - // Мокаем getCards без создания реальных CardModel + reset(mockCardsRepository); when(mockCardsRepository.getCards()).thenAnswer((_) async => []); await chatRoomCubit.checkCards(); @@ -1186,7 +1205,7 @@ void main() { verifyNever(mockChatRepository.sendMessage(data: anyNamed('data'), files: anyNamed('files'), slug: anyNamed('slug'))); }); - test('initChat - вызывает checkCards если consultantId == sender.id', () async { + test('initChat - вызывает checkCards и checkAlfaScoringStatus если consultantId == userId', () async { const uuid = 'room-uuid'; const planId = 123; final chatRoomWithPlan = testChatRoom.copyWith(chosenPlanId: planId); @@ -1215,10 +1234,11 @@ void main() { startDate: DateTime.now(), chosenPlanState: ChosenPlanStateEnum.stateStarted, chatId: 'chat-1', - consultantId: 1, // совпадает с sender.id + consultantId: 1, plan: testPlan, ); + when(mockWsManager.userId).thenReturn(1); when(mockChatRepository.getChatRoom(uuid)).thenAnswer((_) async => chatRoomWithPlan); when(mockChatRepository.getChatMessages(uuid, 1)).thenAnswer((_) async => testChatMessages); when(mockChatRepository.getChosenPlan(id: planId)).thenAnswer((_) async => chosenPlan); @@ -1228,14 +1248,16 @@ void main() { when(mockWsManager.directChatStream).thenReturn(null); when(mockWsManager.clientTypingStream).thenReturn(null); when(mockCardsRepository.getCards()).thenAnswer((_) async => []); + when(mockConsultantRepository.getMyRequestStatus('user-1')).thenAnswer((_) async => requestWithScoring('none')); await chatRoomCubit.initChat(uuid); await Future.delayed(const Duration(milliseconds: 200)); verify(mockCardsRepository.getCards()).called(1); + verify(mockConsultantRepository.getMyRequestStatus('user-1')).called(1); }); - test('initChat - не вызывает checkCards если consultantId != sender.id', () async { + test('initChat - не вызывает checkCards если consultantId != userId', () async { const uuid = 'room-uuid'; const planId = 123; final chatRoomWithPlan = testChatRoom.copyWith(chosenPlanId: planId); @@ -1264,10 +1286,11 @@ void main() { startDate: DateTime.now(), chosenPlanState: ChosenPlanStateEnum.stateStarted, chatId: 'chat-1', - consultantId: 2, // не совпадает с sender.id (1) + consultantId: 2, plan: testPlan, ); + when(mockWsManager.userId).thenReturn(1); when(mockChatRepository.getChatRoom(uuid)).thenAnswer((_) async => chatRoomWithPlan); when(mockChatRepository.getChatMessages(uuid, 1)).thenAnswer((_) async => testChatMessages); when(mockChatRepository.getChosenPlan(id: planId)).thenAnswer((_) async => chosenPlan); @@ -1281,6 +1304,55 @@ void main() { await Future.delayed(const Duration(milliseconds: 200)); verifyNever(mockCardsRepository.getCards()); + verifyNever(mockConsultantRepository.getMyRequestStatus(any)); + }); + + test('checkAlfaScoringStatus - устанавливает isAlfaScoringComplete при scoring_complete', () async { + const uuid = 'room-uuid'; + when(mockChatRepository.getChatRoom(uuid)).thenAnswer((_) async => testChatRoom); + when(mockChatRepository.getChatMessages(uuid, 1)).thenAnswer((_) async => testChatMessages); + when(mockWsManager.client).thenReturn(null); + when(mockWsManager.initPrivateDirectChatChannel(any)).thenAnswer((_) async => Future.value()); + when(mockWsManager.subscribeDirectChatChannel()).thenReturn(null); + when(mockWsManager.directChatStream).thenReturn(null); + when(mockWsManager.clientTypingStream).thenReturn(null); + when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenAnswer((_) async => throw Exception('No plan')); + + await chatRoomCubit.initChat(uuid); + await Future.delayed(const Duration(milliseconds: 200)); + + when(mockConsultantRepository.getMyRequestStatus('user-1')) + .thenAnswer((_) async => requestWithScoring('scoring_complete')); + + await chatRoomCubit.checkAlfaScoringStatus('user-1'); + await Future.delayed(const Duration(milliseconds: 100)); + + final state = chatRoomCubit.state as ChatRoomStateLoaded; + expect(state.isAlfaScoringComplete, true); + }); + + test('checkAlfaScoringStatus - не устанавливает isAlfaScoringComplete при scoring_failed', () async { + const uuid = 'room-uuid'; + when(mockChatRepository.getChatRoom(uuid)).thenAnswer((_) async => testChatRoom); + when(mockChatRepository.getChatMessages(uuid, 1)).thenAnswer((_) async => testChatMessages); + when(mockWsManager.client).thenReturn(null); + when(mockWsManager.initPrivateDirectChatChannel(any)).thenAnswer((_) async => Future.value()); + when(mockWsManager.subscribeDirectChatChannel()).thenReturn(null); + when(mockWsManager.directChatStream).thenReturn(null); + when(mockWsManager.clientTypingStream).thenReturn(null); + when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenAnswer((_) async => throw Exception('No plan')); + + await chatRoomCubit.initChat(uuid); + await Future.delayed(const Duration(milliseconds: 200)); + + when(mockConsultantRepository.getMyRequestStatus('user-1')) + .thenAnswer((_) async => requestWithScoring('scoring_failed')); + + await chatRoomCubit.checkAlfaScoringStatus('user-1'); + await Future.delayed(const Duration(milliseconds: 100)); + + final state = chatRoomCubit.state as ChatRoomStateLoaded; + expect(state.isAlfaScoringComplete, false); }); test('choseStartPlanDate - обрабатывает ошибку', () async {