first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

View File

@@ -0,0 +1,448 @@
//ignore_for_file: file_names, unused_element, unused_local_variable
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:mobile_pos/Provider/product_provider.dart';
import 'package:mobile_pos/Screens/Products/Model/product_total_stock_model.dart';
import 'package:mobile_pos/service/check_user_role_permission_provider.dart';
import 'package:nb_utils/nb_utils.dart';
import '../../../Const/api_config.dart';
import '../../../Repository/constant_functions.dart';
import '../../../constant.dart';
import '../../../core/constant_variables/local_data_saving_keys.dart';
import '../../../http_client/custome_http_client.dart';
import '../../../http_client/customer_http_client_get.dart';
import '../../Purchase/Repo/purchase_repo.dart';
import '../Model/product_model.dart';
import '../add product/modle/create_product_model.dart';
class ProductRepo {
// ==============================================================================
// NEW CREATE PRODUCT FUNCTION
// ==============================================================================
Future<bool> createProduct({required CreateProductModel data, required BuildContext context, required WidgetRef ref}) async {
return _submitProductData(data: data, isUpdate: false, context: context, ref: ref);
}
// ==============================================================================
// NEW UPDATE PRODUCT FUNCTION
// ==============================================================================
Future<bool> updateProduct({required CreateProductModel data, required BuildContext context, required WidgetRef ref}) async {
return _submitProductData(data: data, isUpdate: true, context: context, ref: ref);
}
/// Shared Logic for Create and Update to avoid code duplication
Future<bool> _submitProductData({required CreateProductModel data, required bool isUpdate, required BuildContext context, required WidgetRef ref}) async {
EasyLoading.show(status: isUpdate ? 'Updating Product...' : 'Creating Product...');
final url = Uri.parse(isUpdate ? '${APIConfig.url}/products/${data.productId}' : '${APIConfig.url}/products');
var request = http.MultipartRequest('POST', url);
request.headers.addAll({
'Accept': 'application/json',
'Authorization': await getAuthToken(),
});
// Helper to safely add simple string fields
void addField(String key, dynamic value) {
if (value != null && value.toString().isNotEmpty && value.toString() != 'null') {
request.fields[key] = value.toString();
}
}
// --- 1. Standard Fields ---
if (isUpdate) addField('_method', 'put');
addField('productName', data.name);
addField('category_id', data.categoryId);
addField('unit_id', data.unitId);
addField('productCode', data.productCode);
addField('brand_id', data.brandId);
addField('model_id', data.modelId);
addField('rack_id', data.rackId);
addField('shelf_id', data.shelfId);
addField('alert_qty', data.alertQty);
// Serial logic (1 or 0)
// addField('has_serial', (data.hasSerial == '1' || data.hasSerial == 'true') ? '1' : '0');
addField('product_type', data.productType); // single, variant, combo
addField('vat_type', data.vatType);
addField('vat_id', data.vatId);
// Optional: vat_amount if backend calculates it or needs it
if (data.vatAmount != null) addField('vat_amount', data.vatAmount);
// Extra info
addField('productManufacturer', data.productManufacturer);
addField('productDiscount', data.productDiscount);
// --- 2. Complex Fields (JSON Encoded) ---
// A. STOCKS
// This handles Single (1 item in list) and Variant (multiple items in list)
if (data.stocks != null && data.stocks!.isNotEmpty) {
// Convert list of StockDataModel to List of Maps
List<Map<String, dynamic>> stockListJson = data.stocks!.map((stock) => stock.toJson()).toList();
// Encode to JSON String
request.fields['stocks'] = jsonEncode(stockListJson);
}
// B. VARIATION IDs (Only for variant type)
if (data.productType?.toLowerCase() == 'variant' && (data.variationIds?.isNotEmpty ?? false)) {
request.fields['variation_ids'] = jsonEncode(data.variationIds);
}
// C. COMBO PRODUCTS (Only for combo type)
if (data.productType?.toLowerCase() == 'combo' && (data.comboProducts?.isNotEmpty ?? false)) {
request.fields['combo_products'] = jsonEncode(data.comboProducts);
addField('profit_percent', data.comboProfitPercent);
addField('productSalePrice', data.comboProductSalePrice);
}
// D. WARRANTY & GUARANTEE
Map<String, String> warrantyInfo = {};
if (data.warrantyDuration != null && data.warrantyDuration!.isNotEmpty) {
warrantyInfo['warranty_duration'] = data.warrantyDuration!;
warrantyInfo['warranty_unit'] = data.warrantyPeriod ?? 'days';
}
if (data.guaranteeDuration != null && data.guaranteeDuration!.isNotEmpty) {
warrantyInfo['guarantee_duration'] = data.guaranteeDuration!;
warrantyInfo['guarantee_unit'] = data.guaranteePeriod ?? 'days';
}
if (warrantyInfo.isNotEmpty) {
request.fields['warranty_guarantee_info'] = jsonEncode(warrantyInfo);
}
// --- 3. File Upload ---
if (data.image != null) {
request.files.add(await http.MultipartFile.fromPath(
'productPicture',
data.image!.path,
filename: data.image!.path.split('/').last,
));
}
// --- Debugging Logs ---
print('URL: $url');
print('--- Fields ---');
request.fields.forEach((key, value) {
print('$key: $value');
});
print('--- Fields ---');
print(request.fields);
// --- 4. Execute ---
try {
// var response = await request.send();
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), ref: ref, context: context);
print('Product image: ${data.image?.path}');
final response = await customHttpClient.uploadFile(
url: url,
file: data.image,
fileFieldName: 'productPicture',
fields: request.fields,
);
var responseData = await http.Response.fromStream(response);
EasyLoading.dismiss();
print("Response Status: ${response.statusCode}");
print("Response Body: ${responseData.body}");
if (response.statusCode == 200 || response.statusCode == 201) {
try {
var body = jsonDecode(responseData.body);
EasyLoading.showSuccess(body['message'] ?? (isUpdate ? 'Updated successfully!' : 'Created successfully!'));
return true;
} catch (e) {
// If JSON parsing fails but status is 200
EasyLoading.showSuccess(isUpdate ? 'Product updated!' : 'Product created!');
return true;
}
} else {
try {
var body = jsonDecode(responseData.body);
EasyLoading.showError(body['message'] ?? 'Failed to process product');
} catch (e) {
EasyLoading.showError('Failed with status: ${response.statusCode}');
}
return false;
}
} catch (e) {
EasyLoading.dismiss();
EasyLoading.showError('Network Error: ${e.toString()}');
print(e.toString());
return false;
}
}
Future<String?> generateProductCode() async {
final uri = Uri.parse('${APIConfig.url}/product/generate-code');
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
try {
final response = await clientGet.get(url: uri);
if (response.statusCode == 200) {
final jsonResponse = json.decode(response.body);
return jsonResponse['data'].toString();
} else {
return null;
}
} catch (e) {
return null;
}
}
Future<List<Product>> fetchAllProducts() async {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final uri = Uri.parse('${APIConfig.url}/products');
final response = await clientGet.get(url: uri);
if (response.statusCode == 200) {
final parsedData = jsonDecode(response.body) as Map<String, dynamic>;
final partyList = parsedData['data'] as List<dynamic>;
return partyList.map((category) => Product.fromJson(category)).toList();
// Parse into Party objects
} else {
throw Exception('Failed to fetch Products');
}
}
Future<ProductListResponse> fetchProducts() async {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final uri = Uri.parse('${APIConfig.url}/products');
final response = await clientGet.get(url: uri);
if (response.statusCode == 200) {
final parsedData = jsonDecode(response.body);
return ProductListResponse.fromJson(parsedData);
} else {
throw Exception('Failed to fetch products');
}
}
// Fetch Product Details
Future<Product> fetchProductDetails({required String productID}) async {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final url = Uri.parse('${APIConfig.url}/products/$productID');
try {
var response = await clientGet.get(url: url);
EasyLoading.dismiss();
print(response.statusCode);
print(response.body);
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);
return Product.fromJson(jsonData['data']);
} else {
var data = jsonDecode(response.body);
EasyLoading.showError(data['message'] ?? 'Failed to fetch details');
throw Exception(data['message'] ?? 'Failed to fetch details');
}
} catch (e) {
// Hide loading indicator and show error
EasyLoading.dismiss();
EasyLoading.showError('Error: ${e.toString()}');
throw Exception('Error: ${e.toString()}');
}
}
Future<void> deleteProduct({
required String id,
required BuildContext context,
required WidgetRef ref,
}) async {
final String apiUrl = '${APIConfig.url}/products/$id';
try {
CustomHttpClient customHttpClient = CustomHttpClient(
ref: ref,
context: context,
client: http.Client(),
);
final response = await customHttpClient.delete(
url: Uri.parse(apiUrl),
permission: Permit.productsDelete.value,
);
EasyLoading.dismiss();
// 👇 Print full response info
print('Delete Product Response:');
print('Status Code: ${response.statusCode}');
print('Body: ${response.body}');
print('Headers: ${response.headers}');
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Product deleted successfully')),
);
ref.refresh(productProvider);
} else {
final parsedData = jsonDecode(response.body);
final errorMessage = parsedData['error'].toString().replaceFirst('Exception: ', '');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(errorMessage),
backgroundColor: kMainColor,
),
);
}
} catch (e) {
print('rrrr');
EasyLoading.dismiss();
print('Exception during product delete: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}
Future<bool> addStock({required String id, required String qty}) async {
final url = Uri.parse('${APIConfig.url}/stocks');
String token = await getAuthToken() ?? '';
final headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': token,
};
final requestBody = jsonEncode({
"stock_id": id,
"productStock": qty,
});
try {
final response = await http.post(url, headers: headers, body: requestBody);
if (response.statusCode == 200) {
return true;
} else {
final data = jsonDecode(response.body);
EasyLoading.showError(data['message'] ?? 'Unknown error');
return false;
}
} catch (e) {
EasyLoading.showError('Error: ${e.toString()}');
return false;
}
}
Future<bool> updateVariation({required CartProductModelPurchase data}) async {
EasyLoading.show(status: 'Updating Product...');
final url = Uri.parse('${APIConfig.url}/stocks/${data.variantName}');
var request = http.MultipartRequest('POST', url);
request.headers.addAll({
'Accept': 'application/json',
'Authorization': await getAuthToken(),
});
void addField(String key, dynamic value) {
if (value != null && value.toString().isNotEmpty && value.toString() != 'null') {
request.fields[key] = value.toString();
}
}
// Add standard fields
addField('_method', 'put');
addField('batch_no', data.batchNumber);
addField('productStock', data.quantities);
addField('productPurchasePrice', data.productPurchasePrice);
addField('profit_percent', data.profitPercent);
addField('productSalePrice', data.productSalePrice);
addField('productWholeSalePrice', data.productWholeSalePrice);
addField('productDealerPrice', data.productDealerPrice);
addField('mfg_date', data.mfgDate);
addField('expire_date', data.expireDate);
print('--- Product Data Fields ---');
print('Total fields: ${request.fields.length}');
print(data.mfgDate);
request.fields.forEach((key, value) {
print('$key: $value');
});
try {
var response = await request.send();
var responseData = await http.Response.fromStream(response);
print('Response Status Code: ${response.statusCode}');
print('Response Body: ${responseData.body}');
EasyLoading.dismiss();
if (response.statusCode == 200) {
try {
var body = jsonDecode(responseData.body);
EasyLoading.showSuccess(body['message'] ?? 'Product update successfully!');
return true;
} catch (e) {
EasyLoading.showSuccess('Product update successfully!');
return true;
}
} else {
try {
var body = jsonDecode(responseData.body);
EasyLoading.showError(body['message'] ?? 'Failed to update product');
print('Error Response: ${responseData.body}');
} catch (e) {
EasyLoading.showError('Failed to update product. Status: ${response.statusCode}');
print('Error Response (non-JSON): ${responseData.body}');
}
return false;
}
} catch (e) {
EasyLoading.dismiss();
EasyLoading.showError('Network Error: ${e.toString()}');
print('Network Error: ${e.toString()}');
return false;
}
}
Future<bool> deleteStock({required String id}) async {
EasyLoading.show(status: 'Processing');
final prefs = await SharedPreferences.getInstance();
String token = prefs.getString(LocalDataBaseSavingKey.tokenKey) ?? '';
final url = Uri.parse('${APIConfig.url}/stocks/$id');
final headers = {
'Accept': 'application/json',
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
try {
var response = await http.delete(
url,
headers: headers,
);
EasyLoading.dismiss();
print(response.statusCode);
if (response.statusCode == 200) {
return true;
} else {
var data = jsonDecode(response.body);
EasyLoading.showError(data['message'] ?? 'Failed to delete');
print(data['message']);
return false;
}
} catch (e) {
EasyLoading.dismiss();
EasyLoading.showError('Error: ${e.toString()}');
print(e.toString());
return false;
}
}
}

View File

@@ -0,0 +1,154 @@
// ignore_for_file: file_names, unused_element, unused_local_variable
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import '../../../Const/api_config.dart';
import '../../../Repository/constant_functions.dart';
import '../../../http_client/custome_http_client.dart';
import '../../../http_client/customer_http_client_get.dart';
import '../../product_unit/model/unit_model.dart';
import '../../product_unit/provider/product_unit_provider.dart';
class UnitsRepo {
Future<List<Unit>> fetchAllUnits() async {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final uri = Uri.parse('${APIConfig.url}/units');
try {
final response = await clientGet.get(url: uri);
if (response.statusCode == 200) {
final parsedData = jsonDecode(response.body) as Map<String, dynamic>;
final categoryList = parsedData['data'] as List<dynamic>;
return categoryList.map((unit) => Unit.fromJson(unit)).toList();
} else {
throw Exception('Failed to fetch units: ${response.statusCode}');
}
} catch (error) {
rethrow;
}
}
Future<void> addUnit({
required WidgetRef ref,
required BuildContext context,
required String name,
}) async {
final uri = Uri.parse('${APIConfig.url}/units');
try {
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
var responseData = await customHttpClient.post(
url: uri,
body: {
'unitName': name,
},
// addContentTypeInHeader: true,
);
final parsedData = jsonDecode(responseData.body);
if (responseData.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Added successful!')));
var data1 = ref.refresh(unitsProvider);
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Unit creation failed: ${parsedData['message']}')));
}
} catch (error) {
// Handle unexpected errors gracefully
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('An error occurred: $error')));
}
}
Future<num?> addUnitForBulk({
required String name,
}) async {
final uri = Uri.parse('${APIConfig.url}/units');
try {
var responseData = await http.post(uri, headers: {
"Accept": 'application/json',
'Authorization': await getAuthToken(),
}, body: {
'unitName': name,
});
final parsedData = jsonDecode(responseData.body);
if (responseData.statusCode == 200) {
return parsedData['data']['id'];
} else {
return null;
}
} catch (error) {
return null;
}
}
///_______Edit_Add_________________________________________
Future<void> editUnit({
required WidgetRef ref,
required BuildContext context,
required num id,
required String name,
}) async {
final uri = Uri.parse('${APIConfig.url}/units/$id');
try {
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
var responseData = await customHttpClient.post(
url: uri,
body: {
'unitName': name,
'_method': 'put',
},
);
final parsedData = jsonDecode(responseData.body);
if (responseData.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('update successful!')));
var data1 = ref.refresh(unitsProvider);
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Unit creation failed: ${parsedData['message']}')));
}
} catch (error) {
// Handle unexpected errors gracefully
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('An error occurred: $error')));
}
}
///_________delete_unit________________________
Future<bool> deleteUnit({required BuildContext context, required num unitId, required WidgetRef ref}) async {
final String apiUrl = '${APIConfig.url}/units/$unitId'; // Replace with your API URL
try {
CustomHttpClient customHttpClient = CustomHttpClient(ref: ref, context: context, client: http.Client());
final response = await customHttpClient.delete(
url: Uri.parse(apiUrl),
);
if (response.statusCode == 200) {
final responseData = json.decode(response.body);
final String message = responseData['message'];
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to delete unit.')),
);
return false;
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('An error occurred.')),
);
return false;
}
}
}