save
This commit is contained in:
@@ -78,6 +78,12 @@ class ApiEndpoints {
|
|||||||
/// Response: Product details with stage information
|
/// Response: Product details with stage information
|
||||||
static const String productDetail = '/portalWareHouse/GetProductStageInWareHouse';
|
static const String productDetail = '/portalWareHouse/GetProductStageInWareHouse';
|
||||||
|
|
||||||
|
/// Create product warehouse (import/export)
|
||||||
|
/// POST: /portalWareHouse/createProductWareHouse
|
||||||
|
/// Body: Array of product warehouse creation objects
|
||||||
|
/// Response: Created product warehouse record
|
||||||
|
static const String createProductWarehouse = '/portalWareHouse/createProductWareHouse';
|
||||||
|
|
||||||
/// Get product by ID
|
/// Get product by ID
|
||||||
/// GET: (requires auth token)
|
/// GET: (requires auth token)
|
||||||
/// Parameter: productId
|
/// Parameter: productId
|
||||||
|
|||||||
@@ -81,32 +81,24 @@ class AppRouter {
|
|||||||
),
|
),
|
||||||
|
|
||||||
/// Products List Route
|
/// Products List Route
|
||||||
/// Path: /products
|
/// Path: /products/:warehouseId/:operationType
|
||||||
/// Takes warehouse, warehouseName, and operationType as extra parameter
|
/// Query params: name (warehouse name)
|
||||||
/// Shows products for selected warehouse and operation
|
/// Shows products for selected warehouse and operation
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/products',
|
path: '/products/:warehouseId/:operationType',
|
||||||
name: 'products',
|
name: 'products',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final params = state.extra as Map<String, dynamic>?;
|
// Extract path parameters
|
||||||
|
final warehouseIdStr = state.pathParameters['warehouseId'];
|
||||||
|
final operationType = state.pathParameters['operationType'];
|
||||||
|
|
||||||
if (params == null) {
|
// Extract query parameter
|
||||||
// If no params, redirect to warehouses
|
final warehouseName = state.uri.queryParameters['name'];
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
context.go('/warehouses');
|
|
||||||
});
|
|
||||||
return const _ErrorScreen(
|
|
||||||
message: 'Product parameters are required',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract required parameters
|
// Parse and validate parameters
|
||||||
final warehouse = params['warehouse'] as WarehouseEntity?;
|
final warehouseId = int.tryParse(warehouseIdStr ?? '');
|
||||||
final warehouseName = params['warehouseName'] as String?;
|
|
||||||
final operationType = params['operationType'] as String?;
|
|
||||||
|
|
||||||
// Validate parameters
|
if (warehouseId == null || warehouseName == null || operationType == null) {
|
||||||
if (warehouse == null || warehouseName == null || operationType == null) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
context.go('/warehouses');
|
context.go('/warehouses');
|
||||||
});
|
});
|
||||||
@@ -116,7 +108,7 @@ class AppRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ProductsPage(
|
return ProductsPage(
|
||||||
warehouseId: warehouse.id,
|
warehouseId: warehouseId,
|
||||||
warehouseName: warehouseName,
|
warehouseName: warehouseName,
|
||||||
operationType: operationType,
|
operationType: operationType,
|
||||||
);
|
);
|
||||||
@@ -124,34 +116,28 @@ class AppRouter {
|
|||||||
),
|
),
|
||||||
|
|
||||||
/// Product Detail Route
|
/// Product Detail Route
|
||||||
/// Path: /product-detail
|
/// Path: /product-detail/:warehouseId/:productId/:operationType
|
||||||
/// Takes warehouseId, productId, warehouseName, and optional stageId as extra parameter
|
/// Query params: name (warehouse name), stageId (optional)
|
||||||
/// Shows detailed information for a specific product
|
/// Shows detailed information for a specific product
|
||||||
/// If stageId is provided, only that stage is shown, otherwise all stages are shown
|
/// If stageId is provided, only that stage is shown, otherwise all stages are shown
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/product-detail',
|
path: '/product-detail/:warehouseId/:productId/:operationType',
|
||||||
name: 'product-detail',
|
name: 'product-detail',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final params = state.extra as Map<String, dynamic>?;
|
// Extract path parameters
|
||||||
|
final warehouseIdStr = state.pathParameters['warehouseId'];
|
||||||
|
final productIdStr = state.pathParameters['productId'];
|
||||||
|
final operationType = state.pathParameters['operationType'] ?? 'import';
|
||||||
|
|
||||||
if (params == null) {
|
// Extract query parameters
|
||||||
// If no params, redirect to warehouses
|
final warehouseName = state.uri.queryParameters['name'];
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
final stageIdStr = state.uri.queryParameters['stageId'];
|
||||||
context.go('/warehouses');
|
|
||||||
});
|
|
||||||
return const _ErrorScreen(
|
|
||||||
message: 'Product detail parameters are required',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract required parameters
|
// Parse and validate parameters
|
||||||
final warehouseId = params['warehouseId'] as int?;
|
final warehouseId = int.tryParse(warehouseIdStr ?? '');
|
||||||
final productId = params['productId'] as int?;
|
final productId = int.tryParse(productIdStr ?? '');
|
||||||
final warehouseName = params['warehouseName'] as String?;
|
final stageId = stageIdStr != null ? int.tryParse(stageIdStr) : null;
|
||||||
// Extract optional stageId
|
|
||||||
final stageId = params['stageId'] as int?;
|
|
||||||
|
|
||||||
// Validate parameters
|
|
||||||
if (warehouseId == null || productId == null || warehouseName == null) {
|
if (warehouseId == null || productId == null || warehouseName == null) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
context.go('/warehouses');
|
context.go('/warehouses');
|
||||||
@@ -166,6 +152,7 @@ class AppRouter {
|
|||||||
productId: productId,
|
productId: productId,
|
||||||
warehouseName: warehouseName,
|
warehouseName: warehouseName,
|
||||||
stageId: stageId,
|
stageId: stageId,
|
||||||
|
operationType: operationType,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -360,18 +347,11 @@ extension AppRouterExtension on BuildContext {
|
|||||||
///
|
///
|
||||||
/// [warehouse] - Selected warehouse entity
|
/// [warehouse] - Selected warehouse entity
|
||||||
/// [operationType] - Either 'import' or 'export'
|
/// [operationType] - Either 'import' or 'export'
|
||||||
void goToProducts({
|
void pushToProducts({
|
||||||
required WarehouseEntity warehouse,
|
required WarehouseEntity warehouse,
|
||||||
required String operationType,
|
required String operationType,
|
||||||
}) {
|
}) {
|
||||||
go(
|
push('/products/${warehouse.id}/$operationType?name=${Uri.encodeQueryComponent(warehouse.name)}');
|
||||||
'/products',
|
|
||||||
extra: {
|
|
||||||
'warehouse': warehouse,
|
|
||||||
'warehouseName': warehouse.name,
|
|
||||||
'operationType': operationType,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Navigate to product detail page
|
/// Navigate to product detail page
|
||||||
@@ -379,22 +359,23 @@ extension AppRouterExtension on BuildContext {
|
|||||||
/// [warehouseId] - ID of the warehouse
|
/// [warehouseId] - ID of the warehouse
|
||||||
/// [productId] - ID of the product to view
|
/// [productId] - ID of the product to view
|
||||||
/// [warehouseName] - Name of the warehouse (for display)
|
/// [warehouseName] - Name of the warehouse (for display)
|
||||||
|
/// [operationType] - Either 'import' or 'export'
|
||||||
/// [stageId] - Optional ID of specific stage to show (if null, show all stages)
|
/// [stageId] - Optional ID of specific stage to show (if null, show all stages)
|
||||||
void goToProductDetail({
|
void goToProductDetail({
|
||||||
required int warehouseId,
|
required int warehouseId,
|
||||||
required int productId,
|
required int productId,
|
||||||
required String warehouseName,
|
required String warehouseName,
|
||||||
|
required String operationType,
|
||||||
int? stageId,
|
int? stageId,
|
||||||
}) {
|
}) {
|
||||||
push(
|
final queryParams = <String, String>{
|
||||||
'/product-detail',
|
'name': warehouseName,
|
||||||
extra: {
|
if (stageId != null) 'stageId': stageId.toString(),
|
||||||
'warehouseId': warehouseId,
|
};
|
||||||
'productId': productId,
|
final queryString = queryParams.entries
|
||||||
'warehouseName': warehouseName,
|
.map((e) => '${e.key}=${Uri.encodeQueryComponent(e.value)}')
|
||||||
if (stageId != null) 'stageId': stageId,
|
.join('&');
|
||||||
},
|
push('/product-detail/$warehouseId/$productId/$operationType?$queryString');
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pop current route
|
/// Pop current route
|
||||||
@@ -421,11 +402,13 @@ extension AppRouterNamedExtension on BuildContext {
|
|||||||
}) {
|
}) {
|
||||||
goNamed(
|
goNamed(
|
||||||
'products',
|
'products',
|
||||||
extra: {
|
pathParameters: {
|
||||||
'warehouse': warehouse,
|
'warehouseId': warehouse.id.toString(),
|
||||||
'warehouseName': warehouse.name,
|
|
||||||
'operationType': operationType,
|
'operationType': operationType,
|
||||||
},
|
},
|
||||||
|
queryParameters: {
|
||||||
|
'name': warehouse.name,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import '../../../../core/constants/api_endpoints.dart';
|
|||||||
import '../../../../core/errors/exceptions.dart';
|
import '../../../../core/errors/exceptions.dart';
|
||||||
import '../../../../core/network/api_client.dart';
|
import '../../../../core/network/api_client.dart';
|
||||||
import '../../../../core/network/api_response.dart';
|
import '../../../../core/network/api_response.dart';
|
||||||
|
import '../models/create_product_warehouse_request.dart';
|
||||||
import '../models/product_detail_request_model.dart';
|
import '../models/product_detail_request_model.dart';
|
||||||
import '../models/product_model.dart';
|
import '../models/product_model.dart';
|
||||||
import '../models/product_stage_model.dart';
|
import '../models/product_stage_model.dart';
|
||||||
@@ -24,6 +25,14 @@ abstract class ProductsRemoteDataSource {
|
|||||||
/// Returns List<ProductStageModel> with all stages for the product
|
/// Returns List<ProductStageModel> with all stages for the product
|
||||||
/// Throws [ServerException] if the API call fails
|
/// Throws [ServerException] if the API call fails
|
||||||
Future<List<ProductStageModel>> getProductDetail(ProductDetailRequestModel request);
|
Future<List<ProductStageModel>> getProductDetail(ProductDetailRequestModel request);
|
||||||
|
|
||||||
|
/// Create product warehouse entry (import/export operation)
|
||||||
|
///
|
||||||
|
/// [request] - Request containing all product warehouse details
|
||||||
|
///
|
||||||
|
/// Returns void on success
|
||||||
|
/// Throws [ServerException] if the API call fails
|
||||||
|
Future<void> createProductWarehouse(CreateProductWarehouseRequest request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implementation of ProductsRemoteDataSource using ApiClient
|
/// Implementation of ProductsRemoteDataSource using ApiClient
|
||||||
@@ -126,4 +135,47 @@ class ProductsRemoteDataSourceImpl implements ProductsRemoteDataSource {
|
|||||||
throw ServerException('Failed to get product stages: ${e.toString()}');
|
throw ServerException('Failed to get product stages: ${e.toString()}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> createProductWarehouse(
|
||||||
|
CreateProductWarehouseRequest request) async {
|
||||||
|
try {
|
||||||
|
// The API expects an array of requests
|
||||||
|
final requestData = [request.toJson()];
|
||||||
|
|
||||||
|
// Make API call to create product warehouse
|
||||||
|
final response = await apiClient.post(
|
||||||
|
ApiEndpoints.createProductWarehouse,
|
||||||
|
data: requestData,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parse the API response
|
||||||
|
final apiResponse = ApiResponse.fromJson(
|
||||||
|
response.data as Map<String, dynamic>,
|
||||||
|
(json) => json, // We don't need to parse the response value
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if the API call was successful
|
||||||
|
if (!apiResponse.isSuccess) {
|
||||||
|
// Throw exception with error message from API
|
||||||
|
throw ServerException(
|
||||||
|
apiResponse.errors.isNotEmpty
|
||||||
|
? apiResponse.errors.first
|
||||||
|
: 'Failed to create product warehouse entry',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} 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 create product warehouse entry: ${e.toString()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/// Request model for creating product warehouse (import/export)
|
||||||
|
class CreateProductWarehouseRequest {
|
||||||
|
final int typeId;
|
||||||
|
final int productId;
|
||||||
|
final int stageId;
|
||||||
|
final int? orderId;
|
||||||
|
final String recordDate;
|
||||||
|
final double passedQuantityWeight;
|
||||||
|
final int passedQuantity;
|
||||||
|
final double issuedQuantityWeight;
|
||||||
|
final int issuedQuantity;
|
||||||
|
final int responsibleUserId;
|
||||||
|
final String description;
|
||||||
|
final String productName;
|
||||||
|
final String productCode;
|
||||||
|
final double stockPassedQuantityWeight;
|
||||||
|
final int stockPassedQuantity;
|
||||||
|
final int stockIssuedQuantity;
|
||||||
|
final double stockIssuedQuantityWeight;
|
||||||
|
final int receiverUserId;
|
||||||
|
final int actionTypeId;
|
||||||
|
final int wareHouseId;
|
||||||
|
final int productStageId;
|
||||||
|
final bool isConfirm;
|
||||||
|
|
||||||
|
CreateProductWarehouseRequest({
|
||||||
|
required this.typeId,
|
||||||
|
required this.productId,
|
||||||
|
required this.stageId,
|
||||||
|
this.orderId,
|
||||||
|
required this.recordDate,
|
||||||
|
required this.passedQuantityWeight,
|
||||||
|
required this.passedQuantity,
|
||||||
|
required this.issuedQuantityWeight,
|
||||||
|
required this.issuedQuantity,
|
||||||
|
required this.responsibleUserId,
|
||||||
|
this.description = '',
|
||||||
|
required this.productName,
|
||||||
|
required this.productCode,
|
||||||
|
this.stockPassedQuantityWeight = 0.0,
|
||||||
|
this.stockPassedQuantity = 0,
|
||||||
|
this.stockIssuedQuantity = 0,
|
||||||
|
this.stockIssuedQuantityWeight = 0.0,
|
||||||
|
required this.receiverUserId,
|
||||||
|
required this.actionTypeId,
|
||||||
|
required this.wareHouseId,
|
||||||
|
required this.productStageId,
|
||||||
|
this.isConfirm = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'TypeId': typeId,
|
||||||
|
'ProductId': productId,
|
||||||
|
'StageId': stageId,
|
||||||
|
'OrderId': orderId,
|
||||||
|
'RecordDate': recordDate,
|
||||||
|
'PassedQuantityWeight': passedQuantityWeight,
|
||||||
|
'PassedQuantity': passedQuantity,
|
||||||
|
'IssuedQuantityWeight': issuedQuantityWeight,
|
||||||
|
'IssuedQuantity': issuedQuantity,
|
||||||
|
'ResponsibleUserId': responsibleUserId,
|
||||||
|
'Description': description,
|
||||||
|
'ProductName': productName,
|
||||||
|
'ProductCode': productCode,
|
||||||
|
'StockPassedQuantityWeight': stockPassedQuantityWeight,
|
||||||
|
'StockPassedQuantity': stockPassedQuantity,
|
||||||
|
'StockIssuedQuantity': stockIssuedQuantity,
|
||||||
|
'StockIssuedQuantityWeight': stockIssuedQuantityWeight,
|
||||||
|
'ReceiverUserId': receiverUserId,
|
||||||
|
'ActionTypeId': actionTypeId,
|
||||||
|
'WareHouseId': wareHouseId,
|
||||||
|
'ProductStageId': productStageId,
|
||||||
|
'IsConfirm': isConfirm,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,9 @@ class ProductStageModel extends ProductStageEntity {
|
|||||||
required super.passedQuantityWeight,
|
required super.passedQuantityWeight,
|
||||||
required super.stageName,
|
required super.stageName,
|
||||||
required super.createdDate,
|
required super.createdDate,
|
||||||
|
super.productName,
|
||||||
|
super.productCode,
|
||||||
|
super.stageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Create ProductStageModel from JSON
|
/// Create ProductStageModel from JSON
|
||||||
@@ -27,6 +30,9 @@ class ProductStageModel extends ProductStageEntity {
|
|||||||
passedQuantityWeight: (json['PassedQuantityWeight'] as num).toDouble(),
|
passedQuantityWeight: (json['PassedQuantityWeight'] as num).toDouble(),
|
||||||
stageName: json['StageName'] as String?,
|
stageName: json['StageName'] as String?,
|
||||||
createdDate: json['CreatedDate'] as String,
|
createdDate: json['CreatedDate'] as String,
|
||||||
|
productName: json['ProductName'] as String? ?? '',
|
||||||
|
productCode: json['ProductCode'] as String? ?? '',
|
||||||
|
stageId: json['StageId'] as int?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +48,9 @@ class ProductStageModel extends ProductStageEntity {
|
|||||||
'PassedQuantityWeight': passedQuantityWeight,
|
'PassedQuantityWeight': passedQuantityWeight,
|
||||||
'StageName': stageName,
|
'StageName': stageName,
|
||||||
'CreatedDate': createdDate,
|
'CreatedDate': createdDate,
|
||||||
|
'ProductName': productName,
|
||||||
|
'ProductCode': productCode,
|
||||||
|
'StageId': stageId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +66,9 @@ class ProductStageModel extends ProductStageEntity {
|
|||||||
passedQuantityWeight: passedQuantityWeight,
|
passedQuantityWeight: passedQuantityWeight,
|
||||||
stageName: stageName,
|
stageName: stageName,
|
||||||
createdDate: createdDate,
|
createdDate: createdDate,
|
||||||
|
productName: productName,
|
||||||
|
productCode: productCode,
|
||||||
|
stageId: stageId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import '../../domain/entities/product_entity.dart';
|
|||||||
import '../../domain/entities/product_stage_entity.dart';
|
import '../../domain/entities/product_stage_entity.dart';
|
||||||
import '../../domain/repositories/products_repository.dart';
|
import '../../domain/repositories/products_repository.dart';
|
||||||
import '../datasources/products_remote_datasource.dart';
|
import '../datasources/products_remote_datasource.dart';
|
||||||
|
import '../models/create_product_warehouse_request.dart';
|
||||||
import '../models/product_detail_request_model.dart';
|
import '../models/product_detail_request_model.dart';
|
||||||
|
|
||||||
/// Implementation of ProductsRepository
|
/// Implementation of ProductsRepository
|
||||||
@@ -65,4 +66,26 @@ class ProductsRepositoryImpl implements ProductsRepository {
|
|||||||
return Left(ServerFailure('Unexpected error: ${e.toString()}'));
|
return Left(ServerFailure('Unexpected error: ${e.toString()}'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, void>> createProductWarehouse(
|
||||||
|
CreateProductWarehouseRequest request,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
// Call remote data source to create product warehouse
|
||||||
|
await remoteDataSource.createProductWarehouse(request);
|
||||||
|
|
||||||
|
// Return success
|
||||||
|
return const Right(null);
|
||||||
|
} 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()}'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ class ProductStageEntity extends Equatable {
|
|||||||
final double passedQuantityWeight;
|
final double passedQuantityWeight;
|
||||||
final String? stageName;
|
final String? stageName;
|
||||||
final String createdDate;
|
final String createdDate;
|
||||||
|
final String productName;
|
||||||
|
final String productCode;
|
||||||
|
final int? stageId;
|
||||||
|
|
||||||
const ProductStageEntity({
|
const ProductStageEntity({
|
||||||
required this.productId,
|
required this.productId,
|
||||||
@@ -23,6 +26,9 @@ class ProductStageEntity extends Equatable {
|
|||||||
required this.passedQuantityWeight,
|
required this.passedQuantityWeight,
|
||||||
required this.stageName,
|
required this.stageName,
|
||||||
required this.createdDate,
|
required this.createdDate,
|
||||||
|
this.productName = '',
|
||||||
|
this.productCode = '',
|
||||||
|
this.stageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Get display name for the stage
|
/// Get display name for the stage
|
||||||
@@ -49,6 +55,9 @@ class ProductStageEntity extends Equatable {
|
|||||||
passedQuantityWeight,
|
passedQuantityWeight,
|
||||||
stageName,
|
stageName,
|
||||||
createdDate,
|
createdDate,
|
||||||
|
productName,
|
||||||
|
productCode,
|
||||||
|
stageId,
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import '../../../../core/errors/failures.dart';
|
import '../../../../core/errors/failures.dart';
|
||||||
|
import '../../data/models/create_product_warehouse_request.dart';
|
||||||
import '../entities/product_entity.dart';
|
import '../entities/product_entity.dart';
|
||||||
import '../entities/product_stage_entity.dart';
|
import '../entities/product_stage_entity.dart';
|
||||||
|
|
||||||
@@ -27,4 +28,13 @@ abstract class ProductsRepository {
|
|||||||
int warehouseId,
|
int warehouseId,
|
||||||
int productId,
|
int productId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Create product warehouse entry (import/export operation)
|
||||||
|
///
|
||||||
|
/// [request] - Request containing all product warehouse details
|
||||||
|
///
|
||||||
|
/// Returns Either<Failure, void>
|
||||||
|
Future<Either<Failure, void>> createProductWarehouse(
|
||||||
|
CreateProductWarehouseRequest request,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
|
|
||||||
import '../../../../core/di/providers.dart';
|
import '../../../../core/di/providers.dart';
|
||||||
import '../../../users/domain/entities/user_entity.dart';
|
import '../../../users/domain/entities/user_entity.dart';
|
||||||
|
import '../../data/models/create_product_warehouse_request.dart';
|
||||||
import '../../domain/entities/product_stage_entity.dart';
|
import '../../domain/entities/product_stage_entity.dart';
|
||||||
|
|
||||||
/// Product detail page
|
/// Product detail page
|
||||||
@@ -13,6 +14,7 @@ class ProductDetailPage extends ConsumerStatefulWidget {
|
|||||||
final int productId;
|
final int productId;
|
||||||
final String warehouseName;
|
final String warehouseName;
|
||||||
final int? stageId;
|
final int? stageId;
|
||||||
|
final String operationType;
|
||||||
|
|
||||||
const ProductDetailPage({
|
const ProductDetailPage({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -20,6 +22,7 @@ class ProductDetailPage extends ConsumerStatefulWidget {
|
|||||||
required this.productId,
|
required this.productId,
|
||||||
required this.warehouseName,
|
required this.warehouseName,
|
||||||
this.stageId,
|
this.stageId,
|
||||||
|
this.operationType = 'import',
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -107,17 +110,23 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
final error = productDetailState.error;
|
final error = productDetailState.error;
|
||||||
final selectedIndex = productDetailState.selectedStageIndex;
|
final selectedIndex = productDetailState.selectedStageIndex;
|
||||||
|
|
||||||
|
// Get product name from stages if available
|
||||||
|
final productName = stages.isNotEmpty ? stages.first.productName : 'Product';
|
||||||
|
|
||||||
|
// Capitalize first letter of operation type
|
||||||
|
final operationTitle = widget.operationType == 'import' ? 'Import' : 'Export';
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Column(
|
title: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Product Stages',
|
operationTitle,
|
||||||
style: textTheme.titleMedium,
|
style: textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
widget.warehouseName,
|
productName,
|
||||||
style: textTheme.bodySmall?.copyWith(
|
style: textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
@@ -467,13 +476,26 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// Add button
|
// Action buttons
|
||||||
SizedBox(
|
Row(
|
||||||
width: double.infinity,
|
spacing: 12,
|
||||||
|
children: [
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
onPressed: () => _printQuantities(stageToShow),
|
||||||
|
icon: const Icon(Icons.print),
|
||||||
|
label: const Text('Print'),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
onPressed: () => _addNewQuantities(stageToShow),
|
onPressed: () => _addNewQuantities(stageToShow),
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.save),
|
||||||
label: const Text('Add Quantities'),
|
label: const Text('Save'),
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
),
|
),
|
||||||
@@ -481,6 +503,8 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}(),
|
}(),
|
||||||
),
|
),
|
||||||
@@ -489,7 +513,17 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addNewQuantities(ProductStageEntity stage) {
|
void _printQuantities(ProductStageEntity stage) {
|
||||||
|
// TODO: Implement print functionality
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Print functionality coming soon'),
|
||||||
|
duration: Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _addNewQuantities(ProductStageEntity stage) async {
|
||||||
// Parse the values from text fields
|
// Parse the values from text fields
|
||||||
final passedQuantity = int.tryParse(_passedQuantityController.text) ?? 0;
|
final passedQuantity = int.tryParse(_passedQuantityController.text) ?? 0;
|
||||||
final passedWeight = double.tryParse(_passedWeightController.text) ?? 0.0;
|
final passedWeight = double.tryParse(_passedWeightController.text) ?? 0.0;
|
||||||
@@ -508,31 +542,115 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement API call to add new quantities
|
// Validate that both users are selected
|
||||||
// For now, just show a success message
|
if (_selectedEmployee == null || _selectedWarehouseUser == null) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Please select both Employee and Warehouse User'),
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show loading dialog
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Determine actionTypeId based on operation type
|
||||||
|
// 4 = Import, 5 = Export
|
||||||
|
final typeId = widget.operationType == 'import' ? 4 : 5;
|
||||||
|
final actionTypeId = widget.operationType == 'import' ? 1 : 2;
|
||||||
|
|
||||||
|
// Create request with all required fields
|
||||||
|
final request = CreateProductWarehouseRequest(
|
||||||
|
typeId: typeId, // Import type
|
||||||
|
productId: stage.productId,
|
||||||
|
stageId: stage.stageId ?? 0,
|
||||||
|
orderId: null,
|
||||||
|
recordDate: DateTime.now().toIso8601String(),
|
||||||
|
passedQuantityWeight: passedWeight,
|
||||||
|
passedQuantity: passedQuantity,
|
||||||
|
issuedQuantityWeight: issuedWeight,
|
||||||
|
issuedQuantity: issuedQuantity,
|
||||||
|
responsibleUserId: _selectedEmployee!.id,
|
||||||
|
description: '',
|
||||||
|
productName: stage.productName,
|
||||||
|
productCode: stage.productCode,
|
||||||
|
stockPassedQuantityWeight: stage.passedQuantityWeight,
|
||||||
|
stockPassedQuantity: stage.passedQuantity,
|
||||||
|
stockIssuedQuantity: stage.issuedQuantity,
|
||||||
|
stockIssuedQuantityWeight: stage.issuedQuantityWeight,
|
||||||
|
receiverUserId: _selectedWarehouseUser!.id,
|
||||||
|
actionTypeId: actionTypeId,
|
||||||
|
wareHouseId: widget.warehouseId,
|
||||||
|
productStageId: stage.productStageId ?? 0,
|
||||||
|
isConfirm: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Call the repository to create product warehouse entry
|
||||||
|
final repository = ref.read(productsRepositoryProvider);
|
||||||
|
final result = await repository.createProductWarehouse(request);
|
||||||
|
|
||||||
|
// Dismiss loading dialog
|
||||||
|
if (mounted) Navigator.of(context).pop();
|
||||||
|
|
||||||
|
result.fold(
|
||||||
|
(failure) {
|
||||||
|
// Show error message
|
||||||
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text('Failed to add quantities: ${failure.message}'),
|
||||||
'Added: Passed Q=$passedQuantity, W=$passedWeight | Issued Q=$issuedQuantity, W=$issuedWeight',
|
backgroundColor: Colors.red,
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(_) {
|
||||||
|
// Success - show success message
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Quantities added successfully!'),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Log the values for debugging
|
|
||||||
debugPrint('Adding new quantities for stage ${stage.productStageId}:');
|
|
||||||
debugPrint(' Warehouse User: ${_selectedWarehouseUser?.fullName ?? "Not selected"}');
|
|
||||||
debugPrint(' Warehouse User ID: ${_selectedWarehouseUser?.id}');
|
|
||||||
debugPrint(' Employee: ${_selectedEmployee?.fullName ?? "Not selected"}');
|
|
||||||
debugPrint(' Employee ID: ${_selectedEmployee?.id}');
|
|
||||||
debugPrint(' Passed Quantity: $passedQuantity');
|
|
||||||
debugPrint(' Passed Weight: $passedWeight');
|
|
||||||
debugPrint(' Issued Quantity: $issuedQuantity');
|
|
||||||
debugPrint(' Issued Weight: $issuedWeight');
|
|
||||||
|
|
||||||
// Clear the text fields after successful add
|
// Clear the text fields after successful add
|
||||||
_clearControllers();
|
_clearControllers();
|
||||||
|
|
||||||
|
// Refresh the product detail to show updated quantities
|
||||||
|
ref.read(productDetailProvider(_providerKey).notifier).refreshProductDetail(
|
||||||
|
widget.warehouseId,
|
||||||
|
widget.productId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// Dismiss loading dialog
|
||||||
|
if (mounted) Navigator.of(context).pop();
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Error: ${e.toString()}'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStageHeader(ProductStageEntity stage, ThemeData theme) {
|
Widget _buildStageHeader(ProductStageEntity stage, ThemeData theme) {
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
|
|||||||
warehouseId: widget.warehouseId,
|
warehouseId: widget.warehouseId,
|
||||||
productId: productId,
|
productId: productId,
|
||||||
warehouseName: widget.warehouseName,
|
warehouseName: widget.warehouseName,
|
||||||
|
operationType: widget.operationType,
|
||||||
stageId: stageId,
|
stageId: stageId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -466,6 +467,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
|
|||||||
warehouseId: widget.warehouseId,
|
warehouseId: widget.warehouseId,
|
||||||
productId: product.id,
|
productId: product.id,
|
||||||
warehouseName: widget.warehouseName,
|
warehouseName: widget.warehouseName,
|
||||||
|
operationType: widget.operationType,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
|
||||||
import '../../../../core/di/providers.dart';
|
import '../../../../core/di/providers.dart';
|
||||||
|
import '../../../../core/router/app_router.dart';
|
||||||
import '../widgets/warehouse_card.dart';
|
import '../widgets/warehouse_card.dart';
|
||||||
|
|
||||||
/// Warehouse selection page
|
/// Warehouse selection page
|
||||||
@@ -176,13 +176,9 @@ class _WarehouseSelectionPageState
|
|||||||
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse);
|
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse);
|
||||||
|
|
||||||
// Navigate to products page with warehouse data
|
// Navigate to products page with warehouse data
|
||||||
context.push(
|
context.pushToProducts(
|
||||||
'/products',
|
warehouse: warehouse,
|
||||||
extra: {
|
operationType: 'import', // Default to import
|
||||||
'warehouse': warehouse,
|
|
||||||
'warehouseName': warehouse.name,
|
|
||||||
'operationType': 'import', // Default to import
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user