This commit is contained in:
2025-10-28 00:09:46 +07:00
parent 9ebe7c2919
commit de49f564b1
110 changed files with 15392 additions and 3996 deletions

View 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