Selaa lähdekoodia

Sincronizacion de toppings e imagenes

OscarGil03 6 kuukautta sitten
vanhempi
commit
a21320fb30

+ 1 - 0
lib/models/categoria_producto_model.dart

@@ -45,6 +45,7 @@ class CategoriaProducto extends Basico {
     super.parseJson(json);
     nombre = Basico.parseString(json['nombre']);
     descripcion = Basico.parseString(json['descripcion']);
+    esToping = Basico.parseInt(json['esToping']);
     maximo = Basico.parseInt(json['maximo']);
     creado = Basico.parseDate(json['creado']);
     modificado = Basico.parseDate(json['modificado']);

+ 1 - 0
lib/models/models.dart

@@ -10,6 +10,7 @@ export '../models/pedido_model.dart';
 export '../models/pedido_producto_model.dart';
 export '../models/pedido_producto_toping_model.dart';
 export '../models/producto_model.dart';
+export '../models/producto_topping_model.dart';
 export '../models/toping_categoria_model.dart';
 export '../models/toping_model.dart';
 export '../models/item_carrito_model.dart';

+ 9 - 2
lib/models/producto_model.dart

@@ -18,6 +18,7 @@ class Producto extends Basico {
   int? toping;
   List<Producto>? topings;
   int? activo;
+  List<Media>? media;
 
   Producto({
     super.id,
@@ -34,6 +35,7 @@ class Producto extends Basico {
     this.toping,
     this.topings,
     this.activo,
+    this.media,
   });
 
   @override
@@ -45,7 +47,7 @@ class Producto extends Basico {
     // print("codigo: $codigo, descuento: $descuento, creado: $creado");
     // print("eliminado: $eliminado, modificado: $modificado");
 
-    return {
+    Map<String, dynamic> data = {
       'id': id,
       'idCategoria': idCategoria ?? 0,
       'nombre': nombre ?? '',
@@ -62,7 +64,9 @@ class Producto extends Basico {
       'creado': creado?.toIso8601String(),
       'modificado': modificado?.toIso8601String(),
       'eliminado': eliminado?.toIso8601String(),
-    }..addAll(super.toJson());
+    };
+
+    return data..addAll(super.toJson());
   }
 
   Map<String, dynamic> toMap() {
@@ -130,6 +134,9 @@ class Producto extends Basico {
     creado = Basico.parseDate(json['creado']);
     modificado = Basico.parseDate(json['modificado']);
     eliminado = Basico.parseDate(json['eliminado']);
+    if (json['media'] != null) {
+      media = (json['media'] as List).map((i) => Media.fromJson(i)).toList();
+    }
   }
 
   Future<void> guardar() async {

+ 15 - 2
lib/models/producto_topping_model.dart

@@ -17,15 +17,28 @@ class ProductoTopping extends Basico {
     return {
       'id': id,
       'idProducto': idProducto,
-      'idProductoTopping': idTopping,
-      'idCategoriaTopping': idTopping,
+      'idTopping': idTopping,
+      'idCategoria': idCategoria,
+      'creado': creado?.toIso8601String(),
+      'modificado': modificado?.toIso8601String(),
+      'eliminado': eliminado?.toIso8601String(),
     }..addAll(super.toJson());
   }
 
   ProductoTopping.fromJson(Map<String, dynamic> json) {
     super.parseJson(json);
     idProducto = Basico.parseInt(json['idProducto']);
+    idTopping = Basico.parseInt(json['idTopping']);
+    idCategoria = Basico.parseInt(json['idCategoria']);
+  }
+
+  ProductoTopping.fromApi(Map<String, dynamic> json) {
+    super.parseJson(json);
+    idProducto = Basico.parseInt(json['idProducto']);
     idTopping = Basico.parseInt(json['idProductoTopping']);
     idCategoria = Basico.parseInt(json['idCategoriaTopping']);
+    creado = Basico.parseDate(json['creado']);
+    modificado = Basico.parseDate(json['modificado']);
+    eliminado = Basico.parseDate(json['eliminado']);
   }
 }

+ 4 - 4
lib/services/base_service.dart

@@ -8,11 +8,11 @@ class BaseService {
   int limit = 20;
   int total = 0;
   //String baseUrl = 'hermogas.est.api.rdsistemas.app';
-  //produccion: hermogas.est.api.rdsistemas.app
-  //prueba: hermogas.est.test.rdsistemas.app
+  //produccion: joshipapas.api.edesarrollos.info
+  //prueba: joshipapas.test.edesarrollos.info
 
-  String base_url = 'https://joshipapas.api.edesarrollos.info/';
-  String baseUrl = 'joshipapas.api.edesarrollos.info';
+  String base_url = 'https://joshipapas.test.edesarrollos.info/';
+  String baseUrl = 'joshipapas.test.edesarrollos.info';
   Future<Map<String, String>> getDefaultHeaders({withAuth = true}) async {
     Map<String, String> defaultHeaders = {'Content-Type': 'application/json'};
 

+ 41 - 5
lib/services/repo_service.dart

@@ -555,6 +555,10 @@ class RepoService<T> {
           )
         ''');
 
+          await db.execute('''
+            ALTER TABLE ProductoTopping ADD COLUMN idLocal INTEGER;
+          ''');
+
           break;
       }
       oldVersion++;
@@ -563,11 +567,11 @@ class RepoService<T> {
 
   Future<int> guardar(T model) async {
     try {
-      print("Guardando modelo en la base de datos: ${model.runtimeType}");
+      //print("Guardando modelo en la base de datos: ${model.runtimeType}");
 
       // Convertir el modelo a JSON para la base de datos
       String modelo = json.encode(model, toEncodable: toEncodable);
-      print("Modelo convertido a JSON: $modelo");
+      //print("Modelo convertido a JSON: $modelo");
 
       Map<String, dynamic> modelMap = json.decode(modelo);
       var dbClient = await db;
@@ -588,7 +592,7 @@ class RepoService<T> {
         );
 
         if (existing.isNotEmpty) {
-          print("Actualizando registro existente con ID: $id");
+          //print("Actualizando registro existente con ID: $id");
           await dbClient!.update(
             nombreTabla,
             modelMap,
@@ -615,7 +619,8 @@ class RepoService<T> {
             : [];
 
         if (existing.isNotEmpty) {
-          print("Actualizando registro existente con ID: $id");
+          print(
+              "Actualizando registro existente con ID: $id y modificado: ${modelMap['modificado']}");
           await dbClient!.update(
             nombreTabla,
             modelMap,
@@ -705,7 +710,6 @@ class RepoService<T> {
   }
 
   dynamic toEncodable(dynamic item) {
-    print("Serializando objeto: $item");
     return item.toJson();
   }
 
@@ -1000,6 +1004,38 @@ class RepoService<T> {
     }
   }
 
+  Future<void> sincronizarProductoTopping(
+      List<ProductoTopping> toppingsApi) async {
+    var db = await RepoService().db;
+
+    // Obtenemos los toppings locales existentes
+    var toppingsLocalesQuery = await db!.query('ProductoTopping');
+    List<ProductoTopping> toppingsLocales =
+        toppingsLocalesQuery.map((e) => ProductoTopping.fromJson(e)).toList();
+
+    for (var toppingApi in toppingsApi) {
+      var toppingLocal = toppingsLocales.firstWhere(
+        (topping) => topping.id == toppingApi.id,
+        orElse: () => ProductoTopping(),
+      );
+
+      DateTime? fechaModificadaApi = toppingApi.modificado?.toUtc();
+      DateTime? fechaModificadaLocal = toppingLocal.modificado?.toUtc();
+
+      if (toppingLocal.id == 0) {
+        print("Insertando nuevo topping de producto: ${toppingApi.idProducto}");
+        await RepoService().guardar(toppingApi);
+      } else if (fechaModificadaApi != null &&
+          (fechaModificadaLocal == null ||
+              fechaModificadaApi.isAfter(fechaModificadaLocal))) {
+        print("Actualizando topping de producto: ${toppingApi.idProducto}");
+        await RepoService().guardar(toppingApi);
+      } else {
+        print("Topping sin cambios: ${toppingApi.idProducto}");
+      }
+    }
+  }
+
   Future<void> sincronizarSucursales(List<Sucursal> sucursalesApi) async {
     var db = await RepoService().db;
 

+ 1 - 1
lib/viewmodels/categoria_producto_view_model.dart

@@ -22,7 +22,7 @@ class CategoriaProductoViewModel extends ChangeNotifier {
 
   int _currentPage = 1;
   int _totalProducts = 0;
-  int _limit = 10;
+  int _limit = 20;
 
   int get currentPage => _currentPage;
   int get totalProducts => _totalProducts;

+ 71 - 8
lib/viewmodels/producto_view_model.dart

@@ -6,6 +6,7 @@ import '../services/base_service.dart';
 import '../models/models.dart';
 import '../services/services.dart';
 import '../services/repo_service.dart';
+import '../views/producto/producto_imagen.dart';
 
 class ProductoViewModel<T> extends ChangeNotifier {
   String _busqueda = "";
@@ -182,7 +183,10 @@ class ProductoViewModel<T> extends ChangeNotifier {
     String? claveSucursal = await obtenerClaveSucursal();
 
     try {
-      Map<String, String> parametros = {"claveSucursal": claveSucursal!};
+      Map<String, String> parametros = {
+        "claveSucursal": claveSucursal!,
+        "limite": "-1"
+      };
       final response = ApiResponse(await BaseService()
           .get('/pos/categoria', queryParameters: parametros));
 
@@ -212,7 +216,8 @@ class ProductoViewModel<T> extends ChangeNotifier {
     try {
       Map<String, String> parametros = {
         "limite": "-1",
-        "claveSucursal": claveSucursal!
+        "claveSucursal": claveSucursal!,
+        "expand": "media"
       };
       final response = ApiResponse(await BaseService()
           .get('/pos/producto', queryParameters: parametros));
@@ -222,10 +227,32 @@ class ProductoViewModel<T> extends ChangeNotifier {
             response.resultados!.map((json) => Producto.fromApi(json)).toList();
 
         if (productosApi.isNotEmpty) {
+          print("Productos API obtenidos: ${productosApi.length}");
+
+          // Aquí mantengo tu lógica de descarga de imágenes.
+          for (var productoApi in productosApi) {
+            if (productoApi.media != null && productoApi.media!.isNotEmpty) {
+              for (var media in productoApi.media!) {
+                // Descargar y guardar la imagen localmente
+                String? localImagePath = await downloadAndStoreImage(
+                    media.ruta!, productoApi.id!, media.nombre!);
+
+                if (localImagePath != null) {
+                  productoApi.imagen = localImagePath;
+                }
+              }
+            }
+          }
+
+          // Delegamos la sincronización de los productos al RepoService
           await RepoService().sincronizarProductos(productosApi);
           notifyListeners();
           return true;
+        } else {
+          print("No se encontraron productos en la API.");
         }
+      } else {
+        print("Error en la respuesta de la API o resultados nulos.");
       }
       return false;
     } catch (e) {
@@ -234,25 +261,61 @@ class ProductoViewModel<T> extends ChangeNotifier {
     }
   }
 
+  Future<bool> sincronizarProductoTopping() async {
+    String? claveSucursal = await obtenerClaveSucursal();
+
+    try {
+      Map<String, String> parametros = {
+        "limite": "-1",
+        "claveSucursal": claveSucursal!,
+      };
+
+      final response = ApiResponse(await BaseService()
+          .get('/pos/producto-topping', queryParameters: parametros));
+
+      if (response.isOk && response.resultados != null) {
+        List<ProductoTopping> toppingApi = response.resultados!
+            .map((json) => ProductoTopping.fromApi(json))
+            .toList();
+
+        if (toppingApi.isNotEmpty) {
+          print("Producto Toppings API obtenidos: ${toppingApi.length}");
+
+          // Delegamos la sincronización de los toppings al RepoService
+          await RepoService().sincronizarProductoTopping(toppingApi);
+          notifyListeners();
+          return true;
+        } else {
+          print("No se encontraron toppings de producto en la API.");
+        }
+      } else {
+        print("Error en la respuesta de la API o resultados nulos.");
+      }
+      return false;
+    } catch (e) {
+      print('Error al sincronizar producto toppings: $e');
+      return false;
+    }
+  }
+
   Future<void> sincronizarProductosYCategorias() async {
-    //print('Sincronizando productos');
     setIsLoading(true);
     try {
       bool categoriasSincronizadas = await sincronizarCategorias();
-      //print('Categorias sincronizadas: $categoriasSincronizadas');
 
       if (categoriasSincronizadas) {
         bool productosSincronizados = await sincronizarProductos();
         if (productosSincronizados) {
-          await fetchLocalAll();
+          bool toppingsSincronizados = await sincronizarProductoTopping();
+          if (toppingsSincronizados) {
+            await fetchLocalAll();
+          }
         }
-        //print('Productos sincronizados: $productosSincronizados');
       }
       notifyListeners();
     } catch (e, stackTrace) {
-      // Capturar el mensaje detallado del error
       throw Exception(
-          "Error al sincronizar productos y categorías: $e\n$stackTrace");
+          "Error al sincronizar productos, categorías y toppings: $e\n$stackTrace");
     } finally {
       setIsLoading(false);
     }

+ 1 - 1
lib/views/producto/producto_form.dart

@@ -363,7 +363,7 @@ class Formulario extends State<ProductoForm> {
       id: widget.producto.id,
       nombre: _nombre.text,
       descripcion: _descripcion.text,
-      precio: precio, // Guardando como String para la base de datos
+      precio: precio,
       idCategoria: categoriaProducto!.id,
       imagen: _selectedFilePath,
       topings: selectedToppings.isNotEmpty ? selectedToppings : null,

+ 32 - 4
lib/views/producto/producto_imagen.dart

@@ -1,17 +1,45 @@
 import 'dart:io';
+import 'dart:typed_data';
+import 'package:http/http.dart' as http;
 import 'package:path/path.dart' as path;
 import 'package:path_provider/path_provider.dart';
 import 'package:file_picker/file_picker.dart';
 
+// Guarda la imagen que se añade desde el form
 Future<String?> pickAndStoreImage(File file, int productId) async {
   Directory appDocDir = await getApplicationDocumentsDirectory();
-  String productsDirectoryPath = path.join(appDocDir.path, 'productos');
-  await Directory(productsDirectoryPath).create(recursive: true);
+  String baseDirPath =
+      path.join(appDocDir.path, 'JoshiPos', 'media', 'producto');
+  await Directory(baseDirPath).create(recursive: true);
   String fileExtension = path.extension(file.path);
-  String newFileName = "$productId$fileExtension";
-  String finalPath = path.join(productsDirectoryPath, newFileName);
+  String newFileName =
+      "$productId-${path.basenameWithoutExtension(file.path)}$fileExtension";
+  String finalPath = path.join(baseDirPath, newFileName);
 
   await file.copy(finalPath);
 
   return finalPath;
 }
+
+//Descarga imagen de la sincronizacion del producto
+Future<String?> downloadAndStoreImage(
+    String imageUrl, int productId, String imageName) async {
+  Directory appDocDir = await getApplicationDocumentsDirectory();
+  String baseDirPath =
+      path.join(appDocDir.path, 'JoshiPos', 'media', 'producto');
+  await Directory(baseDirPath).create(recursive: true);
+
+  String fileExtension = path.extension(imageUrl);
+  String newFileName = "$productId-$imageName$fileExtension";
+  String finalPath = path.join(baseDirPath, newFileName);
+
+  http.Response response = await http.get(Uri.parse(imageUrl));
+  if (response.statusCode == 200) {
+    Uint8List bytes = response.bodyBytes;
+    File file = File(finalPath);
+    await file.writeAsBytes(bytes);
+    return finalPath;
+  }
+
+  return null;
+}