fill
This commit is contained in:
398
lib/features/warehouse/ARCHITECTURE.md
Normal file
398
lib/features/warehouse/ARCHITECTURE.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# Warehouse Feature - Architecture Diagram
|
||||
|
||||
## Clean Architecture Layers
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ PRESENTATION LAYER │
|
||||
│ (UI, State Management, User Interactions) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────┐ ┌──────────────────────────────────┐ │
|
||||
│ │ WarehouseCard │ │ WarehouseSelectionPage │ │
|
||||
│ │ - Shows warehouse │ │ - Displays warehouse list │ │
|
||||
│ │ information │ │ - Handles user selection │ │
|
||||
│ └─────────────────────┘ │ - Pull to refresh │ │
|
||||
│ │ - Loading/Error/Empty states │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ WarehouseNotifier │ │
|
||||
│ │ (StateNotifier) │ │
|
||||
│ │ - loadWarehouses() │ │
|
||||
│ │ - selectWarehouse() │ │
|
||||
│ │ - refresh() │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ WarehouseState │ │
|
||||
│ │ - warehouses: List │ │
|
||||
│ │ - selectedWarehouse: Warehouse? │ │
|
||||
│ │ - isLoading: bool │ │
|
||||
│ │ - error: String? │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ uses
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DOMAIN LAYER │
|
||||
│ (Business Logic, Entities, Use Cases) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ GetWarehousesUseCase │ │
|
||||
│ │ - Encapsulates business logic for fetching warehouses │ │
|
||||
│ │ - Single responsibility │ │
|
||||
│ │ - Returns Either<Failure, List<WarehouseEntity>> │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ uses │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseRepository (Interface) │ │
|
||||
│ │ + getWarehouses(): Either<Failure, List<Warehouse>> │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseEntity │ │
|
||||
│ │ - id: int │ │
|
||||
│ │ - name: String │ │
|
||||
│ │ - code: String │ │
|
||||
│ │ - description: String? │ │
|
||||
│ │ - isNGWareHouse: bool │ │
|
||||
│ │ - totalCount: int │ │
|
||||
│ │ + hasItems: bool │ │
|
||||
│ │ + isNGType: bool │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ implements
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DATA LAYER │
|
||||
│ (API Calls, Data Sources, Models) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseRepositoryImpl │ │
|
||||
│ │ - Implements WarehouseRepository interface │ │
|
||||
│ │ - Coordinates data sources │ │
|
||||
│ │ - Converts exceptions to failures │ │
|
||||
│ │ - Maps models to entities │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ uses │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseRemoteDataSource (Interface) │ │
|
||||
│ │ + getWarehouses(): Future<List<WarehouseModel>> │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseRemoteDataSourceImpl │ │
|
||||
│ │ - Makes API calls using ApiClient │ │
|
||||
│ │ - Parses ApiResponse wrapper │ │
|
||||
│ │ - Throws ServerException or NetworkException │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ uses │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseModel │ │
|
||||
│ │ - Extends WarehouseEntity │ │
|
||||
│ │ - Adds JSON serialization (fromJson, toJson) │ │
|
||||
│ │ - Maps API fields to entity fields │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ uses │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ ApiClient (Core) │ │
|
||||
│ │ - Dio HTTP client wrapper │ │
|
||||
│ │ - Adds authentication headers │ │
|
||||
│ │ - Handles 401 errors │ │
|
||||
│ │ - Logging and error handling │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### 1. Loading Warehouses Flow
|
||||
|
||||
```
|
||||
User Action (Pull to Refresh / Page Load)
|
||||
↓
|
||||
WarehouseSelectionPage
|
||||
↓ calls
|
||||
ref.read(warehouseProvider.notifier).loadWarehouses()
|
||||
↓
|
||||
WarehouseNotifier.loadWarehouses()
|
||||
↓ sets state
|
||||
state = state.setLoading() → UI shows loading indicator
|
||||
↓ calls
|
||||
GetWarehousesUseCase.call()
|
||||
↓ calls
|
||||
WarehouseRepository.getWarehouses()
|
||||
↓ calls
|
||||
WarehouseRemoteDataSource.getWarehouses()
|
||||
↓ makes HTTP request
|
||||
ApiClient.get('/warehouses')
|
||||
↓ API Response
|
||||
{
|
||||
"Value": [...],
|
||||
"IsSuccess": true,
|
||||
"IsFailure": false,
|
||||
"Errors": [],
|
||||
"ErrorCodes": []
|
||||
}
|
||||
↓ parse
|
||||
List<WarehouseModel> from JSON
|
||||
↓ convert
|
||||
List<WarehouseEntity>
|
||||
↓ wrap
|
||||
Right(warehouses) or Left(failure)
|
||||
↓ update state
|
||||
state = state.setSuccess(warehouses)
|
||||
↓
|
||||
UI rebuilds with warehouse list
|
||||
```
|
||||
|
||||
### 2. Error Handling Flow
|
||||
|
||||
```
|
||||
API Error / Network Error
|
||||
↓
|
||||
ApiClient throws DioException
|
||||
↓
|
||||
_handleDioError() converts to custom exception
|
||||
↓
|
||||
ServerException or NetworkException
|
||||
↓
|
||||
WarehouseRemoteDataSource catches and rethrows
|
||||
↓
|
||||
WarehouseRepositoryImpl catches exception
|
||||
↓
|
||||
Converts to Failure:
|
||||
- ServerException → ServerFailure
|
||||
- NetworkException → NetworkFailure
|
||||
↓
|
||||
Returns Left(failure)
|
||||
↓
|
||||
GetWarehousesUseCase returns Left(failure)
|
||||
↓
|
||||
WarehouseNotifier receives Left(failure)
|
||||
↓
|
||||
state = state.setError(failure.message)
|
||||
↓
|
||||
UI shows error state with retry button
|
||||
```
|
||||
|
||||
### 3. Warehouse Selection Flow
|
||||
|
||||
```
|
||||
User taps on WarehouseCard
|
||||
↓
|
||||
onTap callback triggered
|
||||
↓
|
||||
_onWarehouseSelected(warehouse)
|
||||
↓
|
||||
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse)
|
||||
↓
|
||||
state = state.setSelectedWarehouse(warehouse)
|
||||
↓
|
||||
Navigation: context.push('/operations', extra: warehouse)
|
||||
↓
|
||||
OperationSelectionPage receives warehouse
|
||||
```
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Riverpod Providers │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ secureStorageProvider │
|
||||
│ ↓ │
|
||||
│ apiClientProvider │
|
||||
│ ↓ │
|
||||
│ warehouseRemoteDataSourceProvider │
|
||||
│ ↓ │
|
||||
│ warehouseRepositoryProvider │
|
||||
│ ↓ │
|
||||
│ getWarehousesUseCaseProvider │
|
||||
│ ↓ │
|
||||
│ warehouseProvider (StateNotifierProvider) │
|
||||
│ ↓ │
|
||||
│ WarehouseSelectionPage watches this provider │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## File Dependencies
|
||||
|
||||
```
|
||||
warehouse_selection_page.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
- warehouse_card.dart
|
||||
- warehouse_provider.dart (via DI setup)
|
||||
|
||||
warehouse_card.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
|
||||
warehouse_provider.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
- get_warehouses_usecase.dart
|
||||
|
||||
get_warehouses_usecase.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
- warehouse_repository.dart (interface)
|
||||
|
||||
warehouse_repository_impl.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
- warehouse_repository.dart (interface)
|
||||
- warehouse_remote_datasource.dart
|
||||
|
||||
warehouse_remote_datasource.dart
|
||||
↓ imports
|
||||
- warehouse_model.dart
|
||||
- api_client.dart
|
||||
- api_response.dart
|
||||
|
||||
warehouse_model.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
```
|
||||
|
||||
## State Transitions
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ Initial │
|
||||
│ isLoading: F │
|
||||
│ error: null │
|
||||
│ warehouses:[]│
|
||||
└──────────────┘
|
||||
↓
|
||||
loadWarehouses()
|
||||
↓
|
||||
┌──────────────┐
|
||||
│ Loading │
|
||||
│ isLoading: T │────────────────┐
|
||||
│ error: null │ │
|
||||
│ warehouses:[]│ │
|
||||
└──────────────┘ │
|
||||
↓ │
|
||||
Success Failure
|
||||
↓ ↓
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ Success │ │ Error │
|
||||
│ isLoading: F │ │ isLoading: F │
|
||||
│ error: null │ │ error: "..." │
|
||||
│ warehouses:[…]│ │ warehouses:[]│
|
||||
└──────────────┘ └──────────────┘
|
||||
↓ ↓
|
||||
Selection Retry
|
||||
↓ ↓
|
||||
┌──────────────┐ (back to Loading)
|
||||
│ Selected │
|
||||
│ selected: W │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
## API Response Parsing
|
||||
|
||||
```
|
||||
Raw API Response (JSON)
|
||||
↓
|
||||
{
|
||||
"Value": [
|
||||
{
|
||||
"Id": 1,
|
||||
"Name": "Warehouse A",
|
||||
"Code": "001",
|
||||
...
|
||||
}
|
||||
],
|
||||
"IsSuccess": true,
|
||||
...
|
||||
}
|
||||
↓
|
||||
ApiResponse.fromJson() parses wrapper
|
||||
↓
|
||||
ApiResponse<List<WarehouseModel>> {
|
||||
value: [WarehouseModel, WarehouseModel, ...],
|
||||
isSuccess: true,
|
||||
isFailure: false,
|
||||
errors: [],
|
||||
errorCodes: []
|
||||
}
|
||||
↓
|
||||
Check isSuccess
|
||||
↓
|
||||
if (isSuccess && value != null)
|
||||
return value!
|
||||
else
|
||||
throw ServerException(errors.first)
|
||||
↓
|
||||
List<WarehouseModel>
|
||||
↓
|
||||
map((model) => model.toEntity())
|
||||
↓
|
||||
List<WarehouseEntity>
|
||||
```
|
||||
|
||||
## Separation of Concerns
|
||||
|
||||
### Domain Layer
|
||||
- **No dependencies** on Flutter, Dio, or other frameworks
|
||||
- Contains **pure business logic**
|
||||
- Defines **contracts** (repository interfaces)
|
||||
- **Independent** and **testable**
|
||||
|
||||
### Data Layer
|
||||
- **Implements** domain contracts
|
||||
- Handles **external dependencies** (API, database)
|
||||
- **Converts** between models and entities
|
||||
- **Transforms** exceptions to failures
|
||||
|
||||
### Presentation Layer
|
||||
- **Depends** only on domain layer
|
||||
- Handles **UI rendering** and **user interactions**
|
||||
- Manages **local state** with Riverpod
|
||||
- **Observes** changes and **reacts** to state updates
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
```
|
||||
Unit Tests
|
||||
├── Domain Layer
|
||||
│ ├── Test entities (equality, methods)
|
||||
│ ├── Test use cases (mock repository)
|
||||
│ └── Verify business logic
|
||||
├── Data Layer
|
||||
│ ├── Test models (JSON serialization)
|
||||
│ ├── Test data sources (mock ApiClient)
|
||||
│ └── Test repository (mock data source)
|
||||
└── Presentation Layer
|
||||
├── Test notifier (mock use case)
|
||||
└── Test state transitions
|
||||
|
||||
Widget Tests
|
||||
├── Test UI rendering
|
||||
├── Test user interactions
|
||||
└── Test state-based UI changes
|
||||
|
||||
Integration Tests
|
||||
├── Test complete flow
|
||||
└── Test with real dependencies
|
||||
```
|
||||
|
||||
## Benefits of This Architecture
|
||||
|
||||
1. **Testability**: Each layer can be tested independently with mocks
|
||||
2. **Maintainability**: Changes in one layer don't affect others
|
||||
3. **Scalability**: Easy to add new features following the same pattern
|
||||
4. **Reusability**: Domain entities and use cases can be reused
|
||||
5. **Separation**: Clear boundaries between UI, business logic, and data
|
||||
6. **Flexibility**: Easy to swap implementations (e.g., change API client)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-10-27
|
||||
**Version:** 1.0.0
|
||||
Reference in New Issue
Block a user