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,76 @@
import '../../../../core/errors/exceptions.dart';
import '../../../../core/network/api_client.dart';
import '../../../../core/network/api_response.dart';
import '../models/warehouse_model.dart';
/// Abstract interface for warehouse remote data source
abstract class WarehouseRemoteDataSource {
/// Get all warehouses from the API
///
/// Returns [List<WarehouseModel>] on success
/// Throws [ServerException] on API errors
/// Throws [NetworkException] on network errors
Future<List<WarehouseModel>> getWarehouses();
}
/// Implementation of warehouse remote data source
/// Uses ApiClient to make HTTP requests to the backend
class WarehouseRemoteDataSourceImpl implements WarehouseRemoteDataSource {
final ApiClient apiClient;
WarehouseRemoteDataSourceImpl(this.apiClient);
@override
Future<List<WarehouseModel>> getWarehouses() async {
try {
// Make POST request to /portalWareHouse/search endpoint
final response = await apiClient.post(
'/portalWareHouse/search',
data: {
'pageIndex': 0,
'pageSize': 100,
'Name': null,
'Code': null,
'sortExpression': null,
'sortDirection': null,
},
);
// Parse the API response wrapper
final apiResponse = ApiResponse.fromJson(
response.data,
(json) {
// Handle the list of warehouses
if (json is List) {
return json.map((e) => WarehouseModel.fromJson(e)).toList();
}
throw const ServerException('Invalid response format: expected List');
},
);
// Check if API call was successful
if (apiResponse.isSuccess && apiResponse.value != null) {
return apiResponse.value!;
} else {
// Extract error message from API response
final errorMessage = apiResponse.errors.isNotEmpty
? apiResponse.errors.first
: 'Failed to get warehouses';
throw ServerException(
errorMessage,
code: apiResponse.firstErrorCode,
);
}
} on ServerException {
rethrow;
} on NetworkException {
rethrow;
} catch (e) {
// Wrap any unexpected errors
throw ServerException(
'Unexpected error while fetching warehouses: ${e.toString()}',
);
}
}
}

View File

@@ -0,0 +1,100 @@
import '../../domain/entities/warehouse_entity.dart';
/// Warehouse data model
/// Extends domain entity and adds JSON serialization
/// Matches the API response format from backend
class WarehouseModel extends WarehouseEntity {
const WarehouseModel({
required super.id,
required super.name,
required super.code,
super.description,
required super.isNGWareHouse,
required super.totalCount,
});
/// Create a WarehouseModel from JSON
///
/// JSON format from API:
/// ```json
/// {
/// "Id": 1,
/// "Name": "Kho nguyên vật liệu",
/// "Code": "001",
/// "Description": "Kho chứa nguyên vật liệu",
/// "IsNGWareHouse": false,
/// "TotalCount": 8
/// }
/// ```
factory WarehouseModel.fromJson(Map<String, dynamic> json) {
return WarehouseModel(
id: json['Id'] ?? 0,
name: json['Name'] ?? '',
code: json['Code'] ?? '',
description: json['Description'],
isNGWareHouse: json['IsNGWareHouse'] ?? false,
totalCount: json['TotalCount'] ?? 0,
);
}
/// Convert model to JSON
Map<String, dynamic> toJson() {
return {
'Id': id,
'Name': name,
'Code': code,
'Description': description,
'IsNGWareHouse': isNGWareHouse,
'TotalCount': totalCount,
};
}
/// Create from domain entity
factory WarehouseModel.fromEntity(WarehouseEntity entity) {
return WarehouseModel(
id: entity.id,
name: entity.name,
code: entity.code,
description: entity.description,
isNGWareHouse: entity.isNGWareHouse,
totalCount: entity.totalCount,
);
}
/// Convert to domain entity
WarehouseEntity toEntity() {
return WarehouseEntity(
id: id,
name: name,
code: code,
description: description,
isNGWareHouse: isNGWareHouse,
totalCount: totalCount,
);
}
/// Create a copy with modified fields
@override
WarehouseModel copyWith({
int? id,
String? name,
String? code,
String? description,
bool? isNGWareHouse,
int? totalCount,
}) {
return WarehouseModel(
id: id ?? this.id,
name: name ?? this.name,
code: code ?? this.code,
description: description ?? this.description,
isNGWareHouse: isNGWareHouse ?? this.isNGWareHouse,
totalCount: totalCount ?? this.totalCount,
);
}
@override
String toString() {
return 'WarehouseModel(id: $id, name: $name, code: $code, totalCount: $totalCount)';
}
}

View File

@@ -0,0 +1,39 @@
import 'package:dartz/dartz.dart';
import '../../../../core/errors/exceptions.dart';
import '../../../../core/errors/failures.dart';
import '../../domain/entities/warehouse_entity.dart';
import '../../domain/repositories/warehouse_repository.dart';
import '../datasources/warehouse_remote_datasource.dart';
/// Implementation of WarehouseRepository
/// Coordinates between data sources and domain layer
/// Converts exceptions to failures for proper error handling
class WarehouseRepositoryImpl implements WarehouseRepository {
final WarehouseRemoteDataSource remoteDataSource;
WarehouseRepositoryImpl(this.remoteDataSource);
@override
Future<Either<Failure, List<WarehouseEntity>>> getWarehouses() async {
try {
// Fetch warehouses from remote data source
final warehouses = await remoteDataSource.getWarehouses();
// Convert models to entities
final entities = warehouses
.map((model) => model.toEntity())
.toList();
return Right(entities);
} on ServerException catch (e) {
// Convert server exceptions to server failures
return Left(ServerFailure(e.message));
} on NetworkException catch (e) {
// Convert network exceptions to network failures
return Left(NetworkFailure(e.message));
} catch (e) {
// Handle any unexpected errors
return Left(ServerFailure('Unexpected error: ${e.toString()}'));
}
}
}