update cart
This commit is contained in:
415
lib/features/cart/CART_API_INTEGRATION.md
Normal file
415
lib/features/cart/CART_API_INTEGRATION.md
Normal file
@@ -0,0 +1,415 @@
|
||||
# Cart API Integration - Complete Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the complete cart API integration following clean architecture principles for the Worker Flutter app.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Presentation Layer (UI)
|
||||
↓
|
||||
Domain Layer (Business Logic)
|
||||
↓
|
||||
Data Layer (API + Local Storage)
|
||||
```
|
||||
|
||||
## Files Created
|
||||
|
||||
### 1. API Constants
|
||||
**File**: `/Users/ssg/project/worker/lib/core/constants/api_constants.dart`
|
||||
|
||||
**Added Endpoints**:
|
||||
- `addToCart` - POST /api/method/building_material.building_material.api.user_cart.add_to_cart
|
||||
- `removeFromCart` - POST /api/method/building_material.building_material.api.user_cart.remove_from_cart
|
||||
- `getUserCart` - POST /api/method/building_material.building_material.api.user_cart.get_user_cart
|
||||
|
||||
### 2. Domain Repository Interface
|
||||
**File**: `/Users/ssg/project/worker/lib/features/cart/domain/repositories/cart_repository.dart`
|
||||
|
||||
**Methods**:
|
||||
- `addToCart()` - Add items to cart
|
||||
- `removeFromCart()` - Remove items from cart
|
||||
- `getCartItems()` - Get all cart items
|
||||
- `updateQuantity()` - Update item quantity
|
||||
- `clearCart()` - Clear all cart items
|
||||
- `getCartTotal()` - Get total cart value
|
||||
- `getCartItemCount()` - Get total item count
|
||||
|
||||
**Returns**: Domain entities (`CartItem`), not models.
|
||||
|
||||
### 3. Remote Data Source
|
||||
**File**: `/Users/ssg/project/worker/lib/features/cart/data/datasources/cart_remote_datasource.dart`
|
||||
|
||||
**Class**: `CartRemoteDataSourceImpl`
|
||||
|
||||
**Features**:
|
||||
- Uses `DioClient` for HTTP requests
|
||||
- Proper error handling with custom exceptions
|
||||
- Converts Dio exceptions to app-specific exceptions
|
||||
- Maps API response to `CartItemModel`
|
||||
|
||||
**API Request/Response Mapping**:
|
||||
|
||||
#### Add to Cart
|
||||
```dart
|
||||
// Request
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"item_id": "Gạch ốp Signature SIG.P-8806",
|
||||
"amount": 4000000,
|
||||
"quantity": 33
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Response
|
||||
{
|
||||
"message": [
|
||||
{
|
||||
"item_id": "Gạch ốp Signature SIG.P-8806",
|
||||
"success": true,
|
||||
"message": "Updated quantity in cart"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Cart Items
|
||||
```dart
|
||||
// Request
|
||||
{
|
||||
"limit_start": 0,
|
||||
"limit_page_length": 0
|
||||
}
|
||||
|
||||
// Response
|
||||
{
|
||||
"message": [
|
||||
{
|
||||
"name": "rfsbgqusrj",
|
||||
"item": "Gạch ốp Signature SIG.P-8806",
|
||||
"quantity": 33.0,
|
||||
"amount": 4000000.0,
|
||||
"item_code": "Gạch ốp Signature SIG.P-8806",
|
||||
"item_name": "Gạch ốp Signature SIG.P-8806",
|
||||
"image": null,
|
||||
"conversion_of_sm": 0.0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Error Handling**:
|
||||
- `TimeoutException` - Connection/send/receive timeout
|
||||
- `NoInternetException` - No network connection
|
||||
- `UnauthorizedException` - 401 errors
|
||||
- `ForbiddenException` - 403 errors
|
||||
- `NotFoundException` - 404 errors
|
||||
- `RateLimitException` - 429 errors
|
||||
- `ServerException` - 5xx errors
|
||||
- `NetworkException` - Other network errors
|
||||
- `ParseException` - Invalid response format
|
||||
|
||||
### 4. Local Data Source
|
||||
**File**: `/Users/ssg/project/worker/lib/features/cart/data/datasources/cart_local_datasource.dart`
|
||||
|
||||
**Class**: `CartLocalDataSourceImpl`
|
||||
|
||||
**Features**:
|
||||
- Uses Hive for local storage
|
||||
- Box name: `cartBox` (from `HiveBoxNames.cartBox`)
|
||||
- Uses `Box<dynamic>` with `.whereType<CartItemModel>()` (best practice)
|
||||
- Stores items with `productId` as key
|
||||
|
||||
**Methods**:
|
||||
- `saveCartItems()` - Replace all cart items
|
||||
- `getCartItems()` - Get all cart items
|
||||
- `addCartItem()` - Add single item (merges if exists)
|
||||
- `updateCartItem()` - Update single item
|
||||
- `removeCartItems()` - Remove items by productId
|
||||
- `clearCart()` - Clear all items
|
||||
- `getCartItemCount()` - Sum of all quantities
|
||||
- `getCartTotal()` - Sum of all subtotals
|
||||
|
||||
**Best Practice**:
|
||||
```dart
|
||||
Box<dynamic> get _cartBox => _hiveService.getBox<dynamic>(HiveBoxNames.cartBox);
|
||||
|
||||
Future<List<CartItemModel>> getCartItems() async {
|
||||
final items = _cartBox.values
|
||||
.whereType<CartItemModel>()
|
||||
.toList();
|
||||
return items;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Repository Implementation
|
||||
**File**: `/Users/ssg/project/worker/lib/features/cart/data/repositories/cart_repository_impl.dart`
|
||||
|
||||
**Class**: `CartRepositoryImpl`
|
||||
|
||||
**Strategy**: API-first with local fallback
|
||||
|
||||
#### Workflow:
|
||||
|
||||
1. **Add to Cart**:
|
||||
- Try API request first
|
||||
- On success: Sync to local storage
|
||||
- On network error: Add to local only + queue for sync
|
||||
- Convert models to entities before returning
|
||||
|
||||
2. **Remove from Cart**:
|
||||
- Try API request first
|
||||
- On success: Remove from local storage
|
||||
- On network error: Remove from local only + queue for sync
|
||||
|
||||
3. **Get Cart Items**:
|
||||
- Try API request first
|
||||
- On success: Sync to local storage
|
||||
- On network error: Return local data (offline support)
|
||||
- Convert models to entities before returning
|
||||
|
||||
4. **Update Quantity**:
|
||||
- Uses `addToCart` with new quantity (replaces existing)
|
||||
|
||||
5. **Clear Cart**:
|
||||
- Gets all items and removes them via API
|
||||
|
||||
**Error Propagation**:
|
||||
- Rethrows: `StorageException`, `NetworkException`, `ServerException`, `ValidationException`
|
||||
- Wraps unknown errors in `UnknownException`
|
||||
|
||||
**Helper Methods**:
|
||||
- `_modelToEntity()` - Convert `CartItemModel` to `CartItem` entity
|
||||
- `_createCartItemModel()` - Create new model from parameters
|
||||
|
||||
### 6. Riverpod Providers
|
||||
|
||||
**Remote Data Source Provider**:
|
||||
```dart
|
||||
@riverpod
|
||||
CartRemoteDataSource cartRemoteDataSource(CartRemoteDataSourceRef ref) {
|
||||
final dioClient = ref.watch(dioClientProvider).requireValue;
|
||||
return CartRemoteDataSourceImpl(dioClient);
|
||||
}
|
||||
```
|
||||
|
||||
**Local Data Source Provider**:
|
||||
```dart
|
||||
@riverpod
|
||||
CartLocalDataSource cartLocalDataSource(CartLocalDataSourceRef ref) {
|
||||
final hiveService = HiveService();
|
||||
return CartLocalDataSourceImpl(hiveService);
|
||||
}
|
||||
```
|
||||
|
||||
**Repository Provider**:
|
||||
```dart
|
||||
@riverpod
|
||||
CartRepository cartRepository(CartRepositoryRef ref) {
|
||||
final remoteDataSource = ref.watch(cartRemoteDataSourceProvider);
|
||||
final localDataSource = ref.watch(cartLocalDataSourceProvider);
|
||||
|
||||
return CartRepositoryImpl(
|
||||
remoteDataSource: remoteDataSource,
|
||||
localDataSource: localDataSource,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
### In Cart Provider (Presentation Layer)
|
||||
|
||||
```dart
|
||||
@riverpod
|
||||
class Cart extends _$Cart {
|
||||
CartRepository get _repository => ref.read(cartRepositoryProvider);
|
||||
|
||||
// Add product to cart via API
|
||||
Future<void> addProductToCart(Product product, double quantity) async {
|
||||
try {
|
||||
// Call repository
|
||||
final items = await _repository.addToCart(
|
||||
itemIds: [product.erpnextItemCode ?? product.productId],
|
||||
quantities: [quantity],
|
||||
prices: [product.basePrice],
|
||||
);
|
||||
|
||||
// Update UI state
|
||||
state = state.copyWith(items: _convertToCartItemData(items));
|
||||
} catch (e) {
|
||||
// Handle error (show snackbar, etc.)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Get cart items from API
|
||||
Future<void> loadCartItems() async {
|
||||
try {
|
||||
final items = await _repository.getCartItems();
|
||||
state = state.copyWith(items: _convertToCartItemData(items));
|
||||
} catch (e) {
|
||||
// Handle error
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from cart via API
|
||||
Future<void> removeProductFromCart(String productId) async {
|
||||
try {
|
||||
await _repository.removeFromCart(itemIds: [productId]);
|
||||
|
||||
// Update UI state
|
||||
final updatedItems = state.items
|
||||
.where((item) => item.product.productId != productId)
|
||||
.toList();
|
||||
state = state.copyWith(items: updatedItems);
|
||||
} catch (e) {
|
||||
// Handle error
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API-First Strategy Details
|
||||
|
||||
### Online Scenario:
|
||||
1. User adds item to cart
|
||||
2. API request sent to backend
|
||||
3. Backend returns updated cart
|
||||
4. Local storage synced with API response
|
||||
5. UI updated with latest data
|
||||
|
||||
### Offline Scenario:
|
||||
1. User adds item to cart
|
||||
2. API request fails (no internet)
|
||||
3. Item added to local storage only
|
||||
4. Request queued for later sync (TODO)
|
||||
5. UI updated from local data
|
||||
|
||||
### Sync on Reconnection:
|
||||
When internet connection restored:
|
||||
1. Process offline queue
|
||||
2. Send queued requests to API
|
||||
3. Sync local storage with API responses
|
||||
4. Clear queue on success
|
||||
|
||||
## Important Notes
|
||||
|
||||
### Product ID Mapping
|
||||
- **Frontend**: Uses `product.productId` (UUID)
|
||||
- **API**: Expects `item_id` (ERPNext item code)
|
||||
- **Mapping**: Use `product.erpnextItemCode` when calling API
|
||||
- **Fallback**: If `erpnextItemCode` is null, use `productId`
|
||||
|
||||
### API Response Fields
|
||||
- `name` - Cart item ID (ERPNext internal ID)
|
||||
- `item` or `item_code` - Product ERPNext code
|
||||
- `quantity` - Item quantity
|
||||
- `amount` - Unit price
|
||||
- `conversion_of_sm` - Conversion factor (if applicable)
|
||||
|
||||
### Local Storage
|
||||
- Box: `HiveBoxNames.cartBox`
|
||||
- Key: `productId` (for easy lookup and updates)
|
||||
- Type: `CartItemModel` (Hive type ID: 5)
|
||||
- Strategy: `Box<dynamic>` with `.whereType<CartItemModel>()`
|
||||
|
||||
### Error Handling Best Practices
|
||||
1. Always catch specific exceptions first
|
||||
2. Rethrow domain-specific exceptions
|
||||
3. Wrap unknown errors in `UnknownException`
|
||||
4. Provide user-friendly error messages
|
||||
5. Log errors for debugging
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Add single item to cart
|
||||
- [ ] Add multiple items at once
|
||||
- [ ] Update item quantity
|
||||
- [ ] Remove single item
|
||||
- [ ] Remove multiple items
|
||||
- [ ] Clear entire cart
|
||||
- [ ] Get cart items
|
||||
- [ ] Calculate cart total
|
||||
- [ ] Calculate item count
|
||||
- [ ] Offline add (no internet)
|
||||
- [ ] Offline remove (no internet)
|
||||
- [ ] Sync after reconnection
|
||||
- [ ] Handle API errors gracefully
|
||||
- [ ] Handle timeout errors
|
||||
- [ ] Handle unauthorized errors
|
||||
- [ ] Local storage persistence
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Offline Queue System**:
|
||||
- Implement request queue for offline operations
|
||||
- Auto-sync when connection restored
|
||||
- Conflict resolution for concurrent edits
|
||||
|
||||
2. **Optimistic Updates**:
|
||||
- Update UI immediately
|
||||
- Sync with backend in background
|
||||
- Rollback on failure
|
||||
|
||||
3. **Cart Sync Status**:
|
||||
- Track sync state per item
|
||||
- Show sync indicators in UI
|
||||
- Manual sync trigger
|
||||
|
||||
4. **Multi-cart Support**:
|
||||
- Named carts (e.g., "Project A", "Project B")
|
||||
- Switch between carts
|
||||
- Merge carts
|
||||
|
||||
5. **Cart Analytics**:
|
||||
- Track add/remove events
|
||||
- Cart abandonment tracking
|
||||
- Conversion metrics
|
||||
|
||||
## Related Files
|
||||
|
||||
- Domain Entity: `/Users/ssg/project/worker/lib/features/cart/domain/entities/cart_item.dart`
|
||||
- Data Model: `/Users/ssg/project/worker/lib/features/cart/data/models/cart_item_model.dart`
|
||||
- UI State: `/Users/ssg/project/worker/lib/features/cart/presentation/providers/cart_state.dart`
|
||||
- Cart Page: `/Users/ssg/project/worker/lib/features/cart/presentation/pages/cart_page.dart`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `dio` - HTTP client
|
||||
- `hive_ce` - Local database
|
||||
- `riverpod` - State management
|
||||
- `riverpod_annotation` - Code generation
|
||||
|
||||
## Code Generation
|
||||
|
||||
To generate Riverpod providers (`.g.dart` files):
|
||||
|
||||
```bash
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
Or for watch mode:
|
||||
|
||||
```bash
|
||||
dart run build_runner watch --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
This implementation provides:
|
||||
- ✅ Clean architecture separation
|
||||
- ✅ API-first with local fallback
|
||||
- ✅ Offline support
|
||||
- ✅ Proper error handling
|
||||
- ✅ Type-safe operations
|
||||
- ✅ Hive best practices
|
||||
- ✅ Riverpod integration
|
||||
- ✅ Scalable and maintainable code
|
||||
|
||||
All files follow the existing codebase patterns and are ready for integration with the UI layer.
|
||||
Reference in New Issue
Block a user