update cart
This commit is contained in:
269
lib/features/cart/data/datasources/cart_remote_datasource.dart
Normal file
269
lib/features/cart/data/datasources/cart_remote_datasource.dart
Normal file
@@ -0,0 +1,269 @@
|
||||
/// Remote Data Source: Cart API
|
||||
///
|
||||
/// Handles all cart-related API requests to the backend.
|
||||
/// Uses Frappe/ERPNext API endpoints for cart operations.
|
||||
library;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import 'package:worker/core/constants/api_constants.dart';
|
||||
import 'package:worker/core/errors/exceptions.dart';
|
||||
import 'package:worker/core/network/dio_client.dart';
|
||||
import 'package:worker/features/cart/data/models/cart_item_model.dart';
|
||||
|
||||
/// Cart Remote Data Source Interface
|
||||
abstract class CartRemoteDataSource {
|
||||
/// Add items to cart
|
||||
///
|
||||
/// [items] - List of items with item_id, quantity, and amount
|
||||
/// Returns list of cart items from API
|
||||
Future<List<CartItemModel>> addToCart({
|
||||
required List<Map<String, dynamic>> items,
|
||||
});
|
||||
|
||||
/// Remove items from cart
|
||||
///
|
||||
/// [itemIds] - List of product ERPNext item codes to remove
|
||||
/// Returns true if successful
|
||||
Future<bool> removeFromCart({
|
||||
required List<String> itemIds,
|
||||
});
|
||||
|
||||
/// Get user's cart items
|
||||
///
|
||||
/// [limitStart] - Pagination offset (default: 0)
|
||||
/// [limitPageLength] - Page size (default: 0 for all)
|
||||
/// Returns list of cart items
|
||||
Future<List<CartItemModel>> getUserCart({
|
||||
int limitStart = 0,
|
||||
int limitPageLength = 0,
|
||||
});
|
||||
}
|
||||
|
||||
/// Cart Remote Data Source Implementation
|
||||
class CartRemoteDataSourceImpl implements CartRemoteDataSource {
|
||||
CartRemoteDataSourceImpl(this._dioClient);
|
||||
|
||||
final DioClient _dioClient;
|
||||
|
||||
@override
|
||||
Future<List<CartItemModel>> addToCart({
|
||||
required List<Map<String, dynamic>> items,
|
||||
}) async {
|
||||
try {
|
||||
// Build request body
|
||||
final requestBody = {
|
||||
'items': items,
|
||||
};
|
||||
|
||||
// Make API request
|
||||
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||
'${ApiConstants.frappeApiMethod}${ApiConstants.addToCart}',
|
||||
data: requestBody,
|
||||
);
|
||||
|
||||
// Check response status
|
||||
if (response.statusCode != 200 && response.statusCode != 201) {
|
||||
throw ServerException(
|
||||
'Failed to add items to cart',
|
||||
response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
// Parse response
|
||||
// Expected format: { "message": [{ "item_id": "...", "success": true, "message": "..." }] }
|
||||
final responseData = response.data;
|
||||
|
||||
if (responseData == null) {
|
||||
throw const ParseException('Invalid response format from add to cart API');
|
||||
}
|
||||
|
||||
// After adding, fetch updated cart
|
||||
return await getUserCart();
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e);
|
||||
} catch (e) {
|
||||
if (e is NetworkException ||
|
||||
e is ServerException ||
|
||||
e is ParseException) {
|
||||
rethrow;
|
||||
}
|
||||
throw UnknownException('Failed to add items to cart', e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> removeFromCart({
|
||||
required List<String> itemIds,
|
||||
}) async {
|
||||
try {
|
||||
// Build request body
|
||||
final requestBody = {
|
||||
'item_ids': itemIds,
|
||||
};
|
||||
|
||||
// Make API request
|
||||
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||
'${ApiConstants.frappeApiMethod}${ApiConstants.removeFromCart}',
|
||||
data: requestBody,
|
||||
);
|
||||
|
||||
// Check response status
|
||||
if (response.statusCode != 200) {
|
||||
throw ServerException(
|
||||
'Failed to remove items from cart',
|
||||
response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
// Parse response
|
||||
// Expected format: { "message": [{ "item_id": "...", "success": true, "message": "..." }] }
|
||||
final responseData = response.data;
|
||||
|
||||
if (responseData == null) {
|
||||
throw const ParseException('Invalid response format from remove from cart API');
|
||||
}
|
||||
|
||||
final message = responseData['message'];
|
||||
if (message is List && message.isNotEmpty) {
|
||||
// Check if all items were removed successfully
|
||||
final allSuccess = message.every((item) => item['success'] == true);
|
||||
return allSuccess;
|
||||
}
|
||||
|
||||
return true;
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e);
|
||||
} catch (e) {
|
||||
if (e is NetworkException ||
|
||||
e is ServerException ||
|
||||
e is ParseException) {
|
||||
rethrow;
|
||||
}
|
||||
throw UnknownException('Failed to remove items from cart', e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<CartItemModel>> getUserCart({
|
||||
int limitStart = 0,
|
||||
int limitPageLength = 0,
|
||||
}) async {
|
||||
try {
|
||||
// Build request body
|
||||
final requestBody = {
|
||||
'limit_start': limitStart,
|
||||
'limit_page_length': limitPageLength,
|
||||
};
|
||||
|
||||
// Make API request
|
||||
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||
'${ApiConstants.frappeApiMethod}${ApiConstants.getUserCart}',
|
||||
data: requestBody,
|
||||
);
|
||||
|
||||
// Check response status
|
||||
if (response.statusCode != 200) {
|
||||
throw ServerException(
|
||||
'Failed to get cart items',
|
||||
response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
// Parse response
|
||||
// Expected format: { "message": [{ "name": "...", "item": "...", "quantity": 0, "amount": 0, ... }] }
|
||||
final responseData = response.data;
|
||||
|
||||
if (responseData == null) {
|
||||
throw const ParseException('Invalid response format from get user cart API');
|
||||
}
|
||||
|
||||
final message = responseData['message'];
|
||||
if (message == null || message is! List) {
|
||||
throw const ParseException('Invalid message format in get user cart response');
|
||||
}
|
||||
|
||||
// Convert to CartItemModel list
|
||||
final cartItems = <CartItemModel>[];
|
||||
for (final item in message) {
|
||||
if (item is! Map<String, dynamic>) continue;
|
||||
|
||||
try {
|
||||
// Map API response to CartItemModel
|
||||
// API fields: name, item, quantity, amount, item_code, item_name, image, conversion_of_sm
|
||||
final cartItem = CartItemModel(
|
||||
cartItemId: item['name'] as String? ?? '',
|
||||
cartId: 'user_cart', // Fixed cart ID for user's cart
|
||||
productId: item['item_code'] as String? ?? item['item'] as String? ?? '',
|
||||
quantity: (item['quantity'] as num?)?.toDouble() ?? 0.0,
|
||||
unitPrice: (item['amount'] as num?)?.toDouble() ?? 0.0,
|
||||
subtotal: ((item['quantity'] as num?)?.toDouble() ?? 0.0) *
|
||||
((item['amount'] as num?)?.toDouble() ?? 0.0),
|
||||
addedAt: DateTime.now(), // API doesn't provide timestamp
|
||||
);
|
||||
|
||||
cartItems.add(cartItem);
|
||||
} catch (e) {
|
||||
// Skip invalid items but don't fail the whole request
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return cartItems;
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e);
|
||||
} catch (e) {
|
||||
if (e is NetworkException ||
|
||||
e is ServerException ||
|
||||
e is ParseException) {
|
||||
rethrow;
|
||||
}
|
||||
throw UnknownException('Failed to get cart items', e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle Dio exceptions and convert to custom exceptions
|
||||
Exception _handleDioException(DioException e) {
|
||||
switch (e.type) {
|
||||
case DioExceptionType.connectionTimeout:
|
||||
case DioExceptionType.sendTimeout:
|
||||
case DioExceptionType.receiveTimeout:
|
||||
return const TimeoutException();
|
||||
|
||||
case DioExceptionType.connectionError:
|
||||
return const NoInternetException();
|
||||
|
||||
case DioExceptionType.badResponse:
|
||||
final statusCode = e.response?.statusCode;
|
||||
if (statusCode != null) {
|
||||
if (statusCode == 401) {
|
||||
return const UnauthorizedException();
|
||||
} else if (statusCode == 403) {
|
||||
return const ForbiddenException();
|
||||
} else if (statusCode == 404) {
|
||||
return const NotFoundException('Cart not found');
|
||||
} else if (statusCode == 429) {
|
||||
return const RateLimitException();
|
||||
} else if (statusCode >= 500) {
|
||||
return ServerException(
|
||||
'Server error: ${e.response?.statusMessage ?? "Unknown error"}',
|
||||
statusCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
return NetworkException(
|
||||
e.response?.statusMessage ?? 'Network error',
|
||||
statusCode: statusCode,
|
||||
);
|
||||
|
||||
case DioExceptionType.cancel:
|
||||
return const NetworkException('Request cancelled');
|
||||
|
||||
case DioExceptionType.badCertificate:
|
||||
return const NetworkException('Invalid SSL certificate');
|
||||
|
||||
case DioExceptionType.unknown:
|
||||
return const NoInternetException();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user