Implement tender workflows and dashboard integration
continuous-integration/drone/push Build encountered an error
continuous-integration/drone/push Build encountered an error
This commit is contained in:
@@ -4,6 +4,9 @@ import 'package:tm_app/data/services/liked_tenders_service.dart';
|
||||
import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart';
|
||||
import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart';
|
||||
import 'package:tm_app/data/services/model/tender_approvals_stats_response/tender_approvals_stats_response.dart';
|
||||
import 'package:tm_app/data/services/model/feedback_stats_response/feedback_stat_response.dart';
|
||||
import 'package:tm_app/data/services/model/tender_submission/tender_submission.dart';
|
||||
import 'package:tm_app/data/services/tender_submissions_service.dart';
|
||||
|
||||
import '../services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
|
||||
import '../services/model/request_models/tender_feedback_request_model/tender_feedback_request_model.dart';
|
||||
@@ -14,12 +17,15 @@ class HomeRepository {
|
||||
required HomeService homeService,
|
||||
required YourTendersService yourTendersService,
|
||||
required LikedTendersService likedTendersService,
|
||||
required TenderSubmissionsService tenderSubmissionsService,
|
||||
}) : _homeService = homeService,
|
||||
_yourTendersService = yourTendersService,
|
||||
_likedTendersService = likedTendersService;
|
||||
_likedTendersService = likedTendersService,
|
||||
_tenderSubmissionsService = tenderSubmissionsService;
|
||||
final HomeService _homeService;
|
||||
final YourTendersService _yourTendersService;
|
||||
final LikedTendersService _likedTendersService;
|
||||
final TenderSubmissionsService _tenderSubmissionsService;
|
||||
|
||||
Future<Result<RecommendedTendersResponse>> getHomeRecommendedTenders({
|
||||
required int limit,
|
||||
@@ -38,6 +44,12 @@ class HomeRepository {
|
||||
return _homeService.getTendersApprovalsStats();
|
||||
}
|
||||
|
||||
Future<Result<FeedbackStatResponse>> getCustomerFeedbackStats() =>
|
||||
_homeService.getFeedbackStats();
|
||||
|
||||
Future<Result<TenderSubmissionStatsResponse>> getSubmissionStats() =>
|
||||
_tenderSubmissionsService.getStats();
|
||||
|
||||
/// Returns how many tenders the current user has liked (not company-wide).
|
||||
Future<Result<int>> getUserLikedTendersCount() async {
|
||||
final result = await _likedTendersService.getLikedTenders(
|
||||
|
||||
@@ -4,17 +4,22 @@ import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_
|
||||
import 'package:tm_app/data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
|
||||
import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart';
|
||||
import 'package:tm_app/data/services/your_tenders_service.dart';
|
||||
import 'package:tm_app/data/services/tender_submissions_service.dart';
|
||||
import 'package:tm_app/data/services/model/tender_submission/tender_submission.dart';
|
||||
|
||||
import '../services/model/request_models/tender_feedback_request_model/tender_feedback_request_model.dart';
|
||||
|
||||
class YourTendersRepository {
|
||||
final YourTendersService _yourTendersService;
|
||||
final LikedTendersService _likedTendersService;
|
||||
final TenderSubmissionsService _tenderSubmissionsService;
|
||||
YourTendersRepository({
|
||||
required YourTendersService yourTendersService,
|
||||
required LikedTendersService likedTendersService,
|
||||
required TenderSubmissionsService tenderSubmissionsService,
|
||||
}) : _yourTendersService = yourTendersService,
|
||||
_likedTendersService = likedTendersService;
|
||||
_likedTendersService = likedTendersService,
|
||||
_tenderSubmissionsService = tenderSubmissionsService;
|
||||
|
||||
Future<Result<TenderApprovalsResponse>> getTenders({
|
||||
required GetTendersRequestModel requestModel,
|
||||
@@ -27,4 +32,41 @@ class YourTendersRepository {
|
||||
}) {
|
||||
return _likedTendersService.getLikedTenders(requestModel: requestModel);
|
||||
}
|
||||
|
||||
Future<Result<TenderSubmissionsResponse>> getSubmissions({
|
||||
required String stage,
|
||||
int limit = 20,
|
||||
int offset = 0,
|
||||
}) => _tenderSubmissionsService.getSubmissions(
|
||||
stages: [stage],
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
);
|
||||
|
||||
Future<Result<TenderSubmissionResponse>> getSubmissionById(String id) =>
|
||||
_tenderSubmissionsService.getById(id);
|
||||
|
||||
Future<Result<TenderSubmissionResponse>> getSubmissionByTender(
|
||||
String tenderId,
|
||||
) => _tenderSubmissionsService.getByTender(tenderId);
|
||||
|
||||
Future<Result<TenderSubmissionResponse>> ensureSubmission(
|
||||
String tenderId, {
|
||||
String? companyId,
|
||||
}) => _tenderSubmissionsService.ensure(tenderId, companyId: companyId);
|
||||
|
||||
Future<Result<TenderSubmissionResponse>> updateSubmissionStatus({
|
||||
required String id,
|
||||
required String status,
|
||||
String? reason,
|
||||
String? description,
|
||||
}) => _tenderSubmissionsService.updateStatus(
|
||||
id: id,
|
||||
status: status,
|
||||
reason: reason,
|
||||
description: description,
|
||||
);
|
||||
|
||||
Future<Result<TenderSubmissionStatsResponse>> getSubmissionStats() =>
|
||||
_tenderSubmissionsService.getStats();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class HomeApi {
|
||||
}
|
||||
|
||||
static const String tenderApprovalStats = '/api/v1/tender-approvals/stats';
|
||||
static const String statsCompany = '/api/v1/feedback/stats/company';
|
||||
static const String statsCustomer = '/api/v1/feedback/stats/customer';
|
||||
static const String checkUnreadNotifications =
|
||||
'/api/v1/notifications?seen=false&limit=1&event_type=PUSH';
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ class LikedTendersApi {
|
||||
required String dateTo,
|
||||
}) {
|
||||
if (dateFrom.isNotEmpty && dateTo.isNotEmpty) {
|
||||
return '$tendersFeedback?feedback_type=$feedbackType&limit=$limit&offset=$offset&date_from=$dateFrom&date_to=$dateTo';
|
||||
return '$tendersFeedback?feedback_type=$feedbackType&limit=$limit&offset=$offset&include_total=true&date_from=$dateFrom&date_to=$dateTo';
|
||||
}
|
||||
return '$tendersFeedback?feedback_type=$feedbackType&limit=$limit&offset=$offset';
|
||||
return '$tendersFeedback?feedback_type=$feedbackType&limit=$limit&offset=$offset&include_total=true';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
class TenderSubmissionsApi {
|
||||
static const String base = '/api/v1/tender-submissions';
|
||||
|
||||
static const String list = base;
|
||||
static const String stats = '$base/stats';
|
||||
static String byId(String id) => '$base/$id';
|
||||
static String byTender(String tenderId) => '$base/tender/$tenderId';
|
||||
static String ensure(String tenderId) => '$base/tender/$tenderId/ensure';
|
||||
static String status(String id) => '$base/$id/status';
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class HomeService {
|
||||
|
||||
Future<Result<FeedbackStatResponse>> getFeedbackStats() async {
|
||||
final result = await _networkManager.makeRequest(
|
||||
HomeApi.statsCompany,
|
||||
HomeApi.statsCustomer,
|
||||
(json) => FeedbackStatResponse.fromJson(json),
|
||||
method: 'GET',
|
||||
);
|
||||
@@ -44,7 +44,7 @@ class HomeService {
|
||||
|
||||
Future<Result<Map<String, dynamic>>> checkUnreadNotifications() async {
|
||||
final result = await _networkManager.makeRequest(
|
||||
HomeApi.checkUnreadNotifications,
|
||||
HomeApi.checkUnreadNotifications,
|
||||
(json) => json,
|
||||
method: 'GET',
|
||||
);
|
||||
|
||||
@@ -22,11 +22,20 @@ class LikedTendersService {
|
||||
dateTo: requestModel.dateTo,
|
||||
);
|
||||
|
||||
final result = await _networkManager.makeRequest(
|
||||
uri,
|
||||
(json) => LikedTendersResponse.fromJson(json),
|
||||
method: 'GET',
|
||||
);
|
||||
final result = await _networkManager.makeRequest(uri, (json) {
|
||||
// Feedback pagination is returned at the envelope root, while the
|
||||
// legacy app model stores it beside data.feedback.
|
||||
final normalized = Map<String, dynamic>.from(json);
|
||||
final data = normalized['data'];
|
||||
final meta = normalized['meta'];
|
||||
if (data is Map && meta is Map) {
|
||||
normalized['data'] = {
|
||||
...Map<String, dynamic>.from(data),
|
||||
'meta': Map<String, dynamic>.from(meta),
|
||||
};
|
||||
}
|
||||
return LikedTendersResponse.fromJson(normalized);
|
||||
}, method: 'GET');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
import 'package:tm_app/core/utils/date_utils.dart';
|
||||
import 'package:tm_app/data/services/model/tender_data/tender_data.dart';
|
||||
|
||||
class TenderSubmissionResponse {
|
||||
const TenderSubmissionResponse({
|
||||
required this.success,
|
||||
required this.message,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
final bool success;
|
||||
final String? message;
|
||||
final TenderSubmission? data;
|
||||
|
||||
factory TenderSubmissionResponse.fromJson(Map<String, dynamic> json) {
|
||||
final data = json['data'];
|
||||
return TenderSubmissionResponse(
|
||||
success: json['success'] == true,
|
||||
message: json['message'] as String?,
|
||||
data:
|
||||
data is Map<String, dynamic> ? TenderSubmission.fromJson(data) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TenderSubmissionsResponse {
|
||||
const TenderSubmissionsResponse({
|
||||
required this.success,
|
||||
required this.message,
|
||||
required this.data,
|
||||
required this.meta,
|
||||
});
|
||||
|
||||
final bool success;
|
||||
final String? message;
|
||||
final List<TenderSubmission> data;
|
||||
final TenderSubmissionsMeta? meta;
|
||||
|
||||
factory TenderSubmissionsResponse.fromJson(Map<String, dynamic> json) {
|
||||
final items = json['data'];
|
||||
return TenderSubmissionsResponse(
|
||||
success: json['success'] == true,
|
||||
message: json['message'] as String?,
|
||||
data:
|
||||
items is List
|
||||
? items
|
||||
.whereType<Map>()
|
||||
.map(
|
||||
(item) => TenderSubmission.fromJson(
|
||||
Map<String, dynamic>.from(item),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
: const [],
|
||||
meta:
|
||||
json['meta'] is Map
|
||||
? TenderSubmissionsMeta.fromJson(
|
||||
Map<String, dynamic>.from(json['meta'] as Map),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TenderSubmission {
|
||||
const TenderSubmission({
|
||||
required this.id,
|
||||
required this.tenderId,
|
||||
required this.companyId,
|
||||
required this.customerId,
|
||||
required this.status,
|
||||
required this.stage,
|
||||
required this.submissionMode,
|
||||
required this.statusHistory,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.tender,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String tenderId;
|
||||
final String? companyId;
|
||||
final String? customerId;
|
||||
final String status;
|
||||
final String stage;
|
||||
final String? submissionMode;
|
||||
final List<TenderSubmissionHistory> statusHistory;
|
||||
final int? createdAt;
|
||||
final int? updatedAt;
|
||||
final TenderData? tender;
|
||||
|
||||
bool get isTerminal =>
|
||||
status == 'rejected' || status == 'contract' || status == 'not_applied';
|
||||
|
||||
factory TenderSubmission.fromJson(Map<String, dynamic> json) {
|
||||
final history = json['status_history'];
|
||||
final tenderJson = json['tender'];
|
||||
return TenderSubmission(
|
||||
id: json['id']?.toString() ?? '',
|
||||
tenderId: json['tender_id']?.toString() ?? '',
|
||||
companyId: json['company_id']?.toString(),
|
||||
customerId: json['customer_id']?.toString(),
|
||||
status: json['status']?.toString() ?? 'opportunity',
|
||||
stage: json['stage']?.toString() ?? 'opportunity',
|
||||
submissionMode: json['submission_mode']?.toString(),
|
||||
statusHistory:
|
||||
history is List
|
||||
? history
|
||||
.whereType<Map>()
|
||||
.map(
|
||||
(item) => TenderSubmissionHistory.fromJson(
|
||||
Map<String, dynamic>.from(item),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
: const [],
|
||||
createdAt: unixTimestampFromJson(json['created_at']),
|
||||
updatedAt: unixTimestampFromJson(json['updated_at']),
|
||||
tender:
|
||||
tenderJson is Map
|
||||
? TenderData.fromJson(Map<String, dynamic>.from(tenderJson))
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TenderSubmissionHistory {
|
||||
const TenderSubmissionHistory({
|
||||
required this.status,
|
||||
required this.reason,
|
||||
required this.changedBy,
|
||||
required this.changedAt,
|
||||
required this.description,
|
||||
});
|
||||
|
||||
final String status;
|
||||
final String? reason;
|
||||
final String? changedBy;
|
||||
final int? changedAt;
|
||||
final String? description;
|
||||
|
||||
factory TenderSubmissionHistory.fromJson(Map<String, dynamic> json) {
|
||||
return TenderSubmissionHistory(
|
||||
status: json['status']?.toString() ?? '',
|
||||
reason: json['reason']?.toString(),
|
||||
changedBy: json['changed_by']?.toString(),
|
||||
changedAt: unixTimestampFromJson(json['changed_at']),
|
||||
description: json['description']?.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TenderSubmissionsMeta {
|
||||
const TenderSubmissionsMeta({
|
||||
required this.total,
|
||||
required this.limit,
|
||||
required this.offset,
|
||||
required this.page,
|
||||
required this.pages,
|
||||
required this.hasMore,
|
||||
});
|
||||
|
||||
final int total;
|
||||
final int limit;
|
||||
final int offset;
|
||||
final int page;
|
||||
final int pages;
|
||||
final bool hasMore;
|
||||
|
||||
factory TenderSubmissionsMeta.fromJson(Map<String, dynamic> json) {
|
||||
int number(String key) => (json[key] as num?)?.toInt() ?? 0;
|
||||
return TenderSubmissionsMeta(
|
||||
total: number('total'),
|
||||
limit: number('limit'),
|
||||
offset: number('offset'),
|
||||
page: number('page'),
|
||||
pages: number('pages'),
|
||||
hasMore: json['has_more'] == true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TenderSubmissionStatsResponse {
|
||||
const TenderSubmissionStatsResponse({
|
||||
required this.success,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
final bool success;
|
||||
final TenderSubmissionStats? data;
|
||||
|
||||
factory TenderSubmissionStatsResponse.fromJson(Map<String, dynamic> json) {
|
||||
return TenderSubmissionStatsResponse(
|
||||
success: json['success'] == true,
|
||||
data:
|
||||
json['data'] is Map
|
||||
? TenderSubmissionStats.fromJson(
|
||||
Map<String, dynamic>.from(json['data'] as Map),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TenderSubmissionStats {
|
||||
const TenderSubmissionStats({
|
||||
required this.companyId,
|
||||
required this.total,
|
||||
required this.byStage,
|
||||
required this.byStatus,
|
||||
required this.lastUpdated,
|
||||
});
|
||||
|
||||
final String? companyId;
|
||||
final int total;
|
||||
final Map<String, int> byStage;
|
||||
final Map<String, int> byStatus;
|
||||
final int? lastUpdated;
|
||||
|
||||
factory TenderSubmissionStats.fromJson(Map<String, dynamic> json) {
|
||||
Map<String, int> counts(value) =>
|
||||
value is Map
|
||||
? value.map(
|
||||
(key, value) => MapEntry(key.toString(), (value as num).toInt()),
|
||||
)
|
||||
: const {};
|
||||
return TenderSubmissionStats(
|
||||
companyId: json['company_id']?.toString(),
|
||||
total: (json['total'] as num?)?.toInt() ?? 0,
|
||||
byStage: counts(json['by_stage']),
|
||||
byStatus: counts(json['by_status']),
|
||||
lastUpdated: unixTimestampFromJson(json['last_updated']),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'package:tm_app/core/network/network_manager.dart';
|
||||
import 'package:tm_app/core/utils/result.dart';
|
||||
import 'package:tm_app/data/services/api/tender_submissions_api.dart';
|
||||
import 'package:tm_app/data/services/model/tender_submission/tender_submission.dart';
|
||||
|
||||
class TenderSubmissionsService {
|
||||
TenderSubmissionsService({required NetworkManager networkManager})
|
||||
: _networkManager = networkManager;
|
||||
|
||||
final NetworkManager _networkManager;
|
||||
|
||||
Future<Result<TenderSubmissionsResponse>> getSubmissions({
|
||||
List<String>? statuses,
|
||||
List<String>? stages,
|
||||
int? createdFrom,
|
||||
int? createdTo,
|
||||
int limit = 20,
|
||||
int offset = 0,
|
||||
String sortBy = 'updated_at',
|
||||
String sortOrder = 'desc',
|
||||
}) {
|
||||
return _networkManager.makeRequest(
|
||||
TenderSubmissionsApi.list,
|
||||
TenderSubmissionsResponse.fromJson,
|
||||
queryParameters: {
|
||||
if (statuses?.isNotEmpty == true) 'status': statuses,
|
||||
if (stages?.isNotEmpty == true) 'stage': stages,
|
||||
if (createdFrom != null) 'created_from': createdFrom,
|
||||
if (createdTo != null) 'created_to': createdTo,
|
||||
'limit': limit,
|
||||
'offset': offset,
|
||||
'sort_by': sortBy,
|
||||
'sort_order': sortOrder,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<Result<TenderSubmissionResponse>> getById(String id) =>
|
||||
_networkManager.makeRequest(
|
||||
TenderSubmissionsApi.byId(id),
|
||||
TenderSubmissionResponse.fromJson,
|
||||
);
|
||||
|
||||
Future<Result<TenderSubmissionResponse>> getByTender(String tenderId) =>
|
||||
_networkManager.makeRequest(
|
||||
TenderSubmissionsApi.byTender(tenderId),
|
||||
TenderSubmissionResponse.fromJson,
|
||||
);
|
||||
|
||||
Future<Result<TenderSubmissionResponse>> ensure(
|
||||
String tenderId, {
|
||||
String? companyId,
|
||||
}) => _networkManager.makeRequest(
|
||||
TenderSubmissionsApi.ensure(tenderId),
|
||||
TenderSubmissionResponse.fromJson,
|
||||
method: 'POST',
|
||||
queryParameters: {
|
||||
if (companyId?.isNotEmpty == true) 'company_id': companyId,
|
||||
},
|
||||
);
|
||||
|
||||
Future<Result<TenderSubmissionResponse>> updateStatus({
|
||||
required String id,
|
||||
required String status,
|
||||
String? reason,
|
||||
String? description,
|
||||
}) => _networkManager.makeRequest(
|
||||
TenderSubmissionsApi.status(id),
|
||||
TenderSubmissionResponse.fromJson,
|
||||
method: 'PATCH',
|
||||
data: {
|
||||
'status': status,
|
||||
if (reason?.trim().isNotEmpty == true) 'reason': reason!.trim(),
|
||||
if (description?.trim().isNotEmpty == true)
|
||||
'description': description!.trim(),
|
||||
},
|
||||
);
|
||||
|
||||
Future<Result<TenderSubmissionStatsResponse>> getStats() =>
|
||||
_networkManager.makeRequest(
|
||||
TenderSubmissionsApi.stats,
|
||||
TenderSubmissionStatsResponse.fromJson,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user