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,62 @@
import '../../../../core/errors/exceptions.dart';
import '../../../../core/network/api_client.dart';
import '../../../../core/network/api_response.dart';
import '../models/product_model.dart';
/// Abstract interface for products remote data source
abstract class ProductsRemoteDataSource {
/// Fetch products from the API
///
/// [warehouseId] - The ID of the warehouse
/// [type] - The operation type ('import' or 'export')
///
/// Returns List<ProductModel>
/// Throws [ServerException] if the API call fails
Future<List<ProductModel>> getProducts(int warehouseId, String type);
}
/// Implementation of ProductsRemoteDataSource using ApiClient
class ProductsRemoteDataSourceImpl implements ProductsRemoteDataSource {
final ApiClient apiClient;
ProductsRemoteDataSourceImpl(this.apiClient);
@override
Future<List<ProductModel>> getProducts(int warehouseId, String type) async {
try {
// Make API call to get all products
final response = await apiClient.get('/portalProduct/getAllProduct');
// Parse the API response using ApiResponse wrapper
final apiResponse = ApiResponse.fromJson(
response.data as Map<String, dynamic>,
(json) => (json as List)
.map((e) => ProductModel.fromJson(e as Map<String, dynamic>))
.toList(),
);
// Check if the API call was successful
if (apiResponse.isSuccess && apiResponse.value != null) {
return apiResponse.value!;
} else {
// Throw exception with error message from API
throw ServerException(
apiResponse.errors.isNotEmpty
? apiResponse.errors.first
: 'Failed to get products',
);
}
} catch (e) {
// Re-throw ServerException as-is
if (e is ServerException) {
rethrow;
}
// Re-throw NetworkException as-is
if (e is NetworkException) {
rethrow;
}
// Wrap other exceptions in ServerException
throw ServerException('Failed to get products: ${e.toString()}');
}
}
}

View File

@@ -0,0 +1,203 @@
import '../../domain/entities/product_entity.dart';
/// Product model - data transfer object
/// Extends ProductEntity and adds serialization capabilities
class ProductModel extends ProductEntity {
const ProductModel({
required super.id,
required super.name,
required super.code,
required super.fullName,
super.description,
super.lotCode,
super.lotNumber,
super.logo,
super.barcode,
required super.quantity,
required super.totalQuantity,
required super.passedQuantity,
super.passedQuantityWeight,
required super.issuedQuantity,
super.issuedQuantityWeight,
required super.piecesInStock,
required super.weightInStock,
required super.weight,
required super.pieces,
required super.conversionRate,
super.percent,
super.price,
required super.isActive,
required super.isConfirm,
super.productStatusId,
required super.productTypeId,
super.orderId,
super.parentId,
super.receiverStageId,
super.order,
super.startDate,
super.endDate,
super.productions,
super.customerProducts,
super.productStages,
super.childrenProducts,
super.productStageWareHouses,
super.productStageDetailWareHouses,
super.productExportExcelSheetDataModels,
super.materialLabels,
super.materials,
super.images,
super.attachmentFiles,
});
/// Create ProductModel from JSON
factory ProductModel.fromJson(Map<String, dynamic> json) {
return ProductModel(
id: json['Id'] ?? 0,
name: json['Name'] ?? '',
code: json['Code'] ?? '',
fullName: json['FullName'] ?? '',
description: json['Description'],
lotCode: json['LotCode'],
lotNumber: json['LotNumber'],
logo: json['Logo'],
barcode: json['Barcode'],
quantity: json['Quantity'] ?? 0,
totalQuantity: json['TotalQuantity'] ?? 0,
passedQuantity: json['PassedQuantity'] ?? 0,
passedQuantityWeight: json['PassedQuantityWeight']?.toDouble(),
issuedQuantity: json['IssuedQuantity'] ?? 0,
issuedQuantityWeight: json['IssuedQuantityWeight']?.toDouble(),
piecesInStock: json['PiecesInStock'] ?? 0,
weightInStock: (json['WeightInStock'] ?? 0).toDouble(),
weight: (json['Weight'] ?? 0).toDouble(),
pieces: json['Pieces'] ?? 0,
conversionRate: (json['ConversionRate'] ?? 0).toDouble(),
percent: json['Percent']?.toDouble(),
price: json['Price']?.toDouble(),
isActive: json['IsActive'] ?? true,
isConfirm: json['IsConfirm'] ?? false,
productStatusId: json['ProductStatusId'],
productTypeId: json['ProductTypeId'] ?? 0,
orderId: json['OrderId'],
parentId: json['ParentId'],
receiverStageId: json['ReceiverStageId'],
order: json['Order'],
startDate: json['StartDate'],
endDate: json['EndDate'],
productions: json['Productions'] ?? [],
customerProducts: json['CustomerProducts'] ?? [],
productStages: json['ProductStages'] ?? [],
childrenProducts: json['ChildrenProducts'],
productStageWareHouses: json['ProductStageWareHouses'],
productStageDetailWareHouses: json['ProductStageDetailWareHouses'],
productExportExcelSheetDataModels:
json['ProductExportExcelSheetDataModels'],
materialLabels: json['MaterialLabels'],
materials: json['Materials'],
images: json['Images'],
attachmentFiles: json['AttachmentFiles'],
);
}
/// Convert ProductModel to JSON
Map<String, dynamic> toJson() {
return {
'Id': id,
'Name': name,
'Code': code,
'FullName': fullName,
'Description': description,
'LotCode': lotCode,
'LotNumber': lotNumber,
'Logo': logo,
'Barcode': barcode,
'Quantity': quantity,
'TotalQuantity': totalQuantity,
'PassedQuantity': passedQuantity,
'PassedQuantityWeight': passedQuantityWeight,
'IssuedQuantity': issuedQuantity,
'IssuedQuantityWeight': issuedQuantityWeight,
'PiecesInStock': piecesInStock,
'WeightInStock': weightInStock,
'Weight': weight,
'Pieces': pieces,
'ConversionRate': conversionRate,
'Percent': percent,
'Price': price,
'IsActive': isActive,
'IsConfirm': isConfirm,
'ProductStatusId': productStatusId,
'ProductTypeId': productTypeId,
'OrderId': orderId,
'ParentId': parentId,
'ReceiverStageId': receiverStageId,
'Order': order,
'StartDate': startDate,
'EndDate': endDate,
'Productions': productions,
'CustomerProducts': customerProducts,
'ProductStages': productStages,
'ChildrenProducts': childrenProducts,
'ProductStageWareHouses': productStageWareHouses,
'ProductStageDetailWareHouses': productStageDetailWareHouses,
'ProductExportExcelSheetDataModels': productExportExcelSheetDataModels,
'MaterialLabels': materialLabels,
'Materials': materials,
'Images': images,
'AttachmentFiles': attachmentFiles,
};
}
/// Convert ProductModel to ProductEntity
ProductEntity toEntity() => this;
/// Create ProductModel from ProductEntity
factory ProductModel.fromEntity(ProductEntity entity) {
return ProductModel(
id: entity.id,
name: entity.name,
code: entity.code,
fullName: entity.fullName,
description: entity.description,
lotCode: entity.lotCode,
lotNumber: entity.lotNumber,
logo: entity.logo,
barcode: entity.barcode,
quantity: entity.quantity,
totalQuantity: entity.totalQuantity,
passedQuantity: entity.passedQuantity,
passedQuantityWeight: entity.passedQuantityWeight,
issuedQuantity: entity.issuedQuantity,
issuedQuantityWeight: entity.issuedQuantityWeight,
piecesInStock: entity.piecesInStock,
weightInStock: entity.weightInStock,
weight: entity.weight,
pieces: entity.pieces,
conversionRate: entity.conversionRate,
percent: entity.percent,
price: entity.price,
isActive: entity.isActive,
isConfirm: entity.isConfirm,
productStatusId: entity.productStatusId,
productTypeId: entity.productTypeId,
orderId: entity.orderId,
parentId: entity.parentId,
receiverStageId: entity.receiverStageId,
order: entity.order,
startDate: entity.startDate,
endDate: entity.endDate,
productions: entity.productions,
customerProducts: entity.customerProducts,
productStages: entity.productStages,
childrenProducts: entity.childrenProducts,
productStageWareHouses: entity.productStageWareHouses,
productStageDetailWareHouses: entity.productStageDetailWareHouses,
productExportExcelSheetDataModels:
entity.productExportExcelSheetDataModels,
materialLabels: entity.materialLabels,
materials: entity.materials,
images: entity.images,
attachmentFiles: entity.attachmentFiles,
);
}
}

View File

@@ -0,0 +1,37 @@
import 'package:dartz/dartz.dart';
import '../../../../core/errors/exceptions.dart';
import '../../../../core/errors/failures.dart';
import '../../domain/entities/product_entity.dart';
import '../../domain/repositories/products_repository.dart';
import '../datasources/products_remote_datasource.dart';
/// Implementation of ProductsRepository
/// Handles data operations and error conversion
class ProductsRepositoryImpl implements ProductsRepository {
final ProductsRemoteDataSource remoteDataSource;
ProductsRepositoryImpl(this.remoteDataSource);
@override
Future<Either<Failure, List<ProductEntity>>> getProducts(
int warehouseId,
String type,
) async {
try {
// Fetch products from remote data source
final products = await remoteDataSource.getProducts(warehouseId, type);
// Convert models to entities and return success
return Right(products.map((model) => model.toEntity()).toList());
} on ServerException catch (e) {
// Convert ServerException to ServerFailure
return Left(ServerFailure(e.message));
} on NetworkException catch (e) {
// Convert NetworkException to NetworkFailure
return Left(NetworkFailure(e.message));
} catch (e) {
// Handle any other exceptions
return Left(ServerFailure('Unexpected error: ${e.toString()}'));
}
}
}