Преглед на файлове

WIP: Inserimento ordini

Fabio Dalle Ave преди 2 месеца
родител
ревизия
4e2712d7cf
променени са 33 файла, в които са добавени 9596 реда и са изтрити 3354 реда
  1. 0 0
      .flutter-plugins-dependencies
  2. 8 4
      lib/business/order_manager.dart
  3. 1 1
      lib/core/constants.dart
  4. 1 1
      lib/core/responsive.dart
  5. 5 1
      lib/database/daos/agenti_dao.dart
  6. 147 71
      lib/database/daos/articoli_dao.dart
  7. 62 47
      lib/database/daos/ordini_dao.dart
  8. 409 725
      lib/database/database.g.dart
  9. 18 2
      lib/database/db_connector_web.dart
  10. 64 0
      lib/database/sql_logger.dart
  11. 2 2
      lib/database/tables/agenti.dart
  12. 19 0
      lib/database/views/v_ordini.dart
  13. 477 490
      lib/dto/sync_customer_dto.freezed.dart
  14. 98 98
      lib/dto/sync_customer_dto.g.dart
  15. 2 2
      lib/dto/sync_order_dto.dart
  16. 507 530
      lib/dto/sync_order_dto.freezed.dart
  17. 45 46
      lib/dto/sync_order_dto.g.dart
  18. 10 10
      lib/dto/sync_promo_dto.dart
  19. 381 427
      lib/dto/sync_promo_dto.freezed.dart
  20. 113 116
      lib/dto/sync_promo_dto.g.dart
  21. 1 1
      lib/dto/sync_response_dto.dart
  22. 91 106
      lib/dto/sync_response_dto.freezed.dart
  23. 22 22
      lib/dto/sync_response_dto.g.dart
  24. 53 25
      lib/providers/order_provider.dart
  25. 43 543
      lib/ui/pages/orders/order_edit_page.dart
  26. 35 30
      lib/ui/pages/orders/order_list_page.dart
  27. 647 0
      lib/ui/pages/orders/selezione_articolo_page.dart
  28. 68 53
      lib/ui/pages/sync/sync_page.dart
  29. 55 0
      pubspec copy.yaml
  30. 1 1
      pubspec.yaml
  31. 3105 0
      web/drift_worker.dart.js
  32. 3106 0
      web/drift_worker.js
  33. BIN
      web/sqlite3.wasm

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
.flutter-plugins-dependencies


+ 8 - 4
lib/business/order_manager.dart

@@ -108,7 +108,8 @@ class OrderManager {
     return null;
   }
 
-  static double calcPrezzoNetto(double lordo, double s1, double s2, double s3, double s4, double s5) {
+  static double calcPrezzoNetto(
+      double lordo, double s1, double s2, double s3, double s4, double s5) {
     double netto = lordo;
     netto *= (100 - s1) / 100;
     netto *= (100 - s2) / 100;
@@ -118,7 +119,8 @@ class OrderManager {
     return netto;
   }
 
-  Future<List<RigaOrdine>> getOrderLines(int idOrdine) => _ordiniDao.getOrderLines(idOrdine);
+  Future<List<RigaOrdine>> getOrderLines(int idOrdine) =>
+      _ordiniDao.getOrderLines(idOrdine);
 
   Future<int> addOrderLine(RigaOrdine line) async {
     if (_currentOrder == null) return 0;
@@ -154,6 +156,8 @@ class OrderManager {
     _isChanged = false;
   }
 
-  Future<double> getOrderTotal(int idOrdine) => _ordiniDao.totalAmountItems(idOrdine);
-  Future<double> getOrderQty(int idOrdine) => _ordiniDao.totalQtyItems(idOrdine);
+  Future<double> getOrderTotal(int idOrdine) =>
+      _ordiniDao.totalAmountItems(idOrdine);
+  Future<double> getOrderQty(int idOrdine) =>
+      _ordiniDao.totalQtyItems(idOrdine);
 }

+ 1 - 1
lib/core/constants.dart

@@ -8,7 +8,7 @@ class AppConstants {
   static const int kMaxOrdersInList = 200;
   static const int kInactivityDays = 92;
   static const String kDbName = 'Ristoris.db';
-  static const String kAssetsDbPath = 'db/Ristoris.db';
+  static const String kAssetsDbPath = 'assets/db/Ristoris.db';
   static const String kDbVersionParam = 'DB_VERSION';
   static const String kUrlParam = 'URL';
   static const String kLastUpdateParam = 'lastUpdate';

+ 1 - 1
lib/core/responsive.dart

@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 
 class ResponsiveBreakpoints {
-  static const double phone = 600;
+  static const double phone = 700;
   static const double tablet = 900;
 
   static bool isPhone(BuildContext context) =>

+ 5 - 1
lib/database/daos/agenti_dao.dart

@@ -9,12 +9,16 @@ class AgentiDao {
     return (db.select(db.agenti)..where((t) => t.idAgente.equals(id))).getSingleOrNull();
   }
 
+  Future<List<Agente>> listAll() {
+    return (db.select(db.agenti)).get();
+  }
+
   Future<Agente?> findByCode(String code) {
     return (db.select(db.agenti)..where((t) => t.codAgente.equals(code))).getSingleOrNull();
   }
 
   Future<Agente?> authenticate(String code, String password) {
-    return (db.select(db.agenti)
+    return  (db.select(db.agenti)
       ..where((t) => t.codAgente.equals(code) & t.pwd.equals(password))
     ).getSingleOrNull();
   }

+ 147 - 71
lib/database/daos/articoli_dao.dart

@@ -1,10 +1,5 @@
 import 'package:drift/drift.dart';
 import '../database.dart';
-import '../tables/articoli.dart';
-import '../tables/listini.dart';
-import '../tables/listini_articolo.dart';
-import '../tables/listini_cliente.dart';
-import '../views/top_item.dart';
 
 class ArticoliDao {
   final AppDatabase db;
@@ -13,7 +8,8 @@ class ArticoliDao {
   Future<Articolo?> getByCode(String code) {
     return (db.select(
       db.articoli,
-    )..where((t) => t.codArticolo.equals(code))).getSingleOrNull();
+    )..where((t) => t.codArticolo.equals(code)))
+        .getSingleOrNull();
   }
 
   Future<List<Articolo>> getItems(String filter) async {
@@ -50,13 +46,14 @@ class ArticoliDao {
   }
 
   Future<List<Listino>> getListini() {
-    return (db.select(db.listini)..where(
-          (t) =>
-              t.codListino.isNotNull() &
-              t.codListino.like('OF2').not() &
-              t.codListino.like('OF4').not() &
-              t.codListino.like('I0').not(),
-        ))
+    return (db.select(db.listini)
+          ..where(
+            (t) =>
+                t.codListino.isNotNull() &
+                t.codListino.like('OF2').not() &
+                t.codListino.like('OF4').not() &
+                t.codListino.like('I0').not(),
+          ))
         .get();
   }
 
@@ -71,12 +68,20 @@ class ArticoliDao {
     int idArticolo,
     String date,
   ) {
-    return (db.select(db.listiniArticolo)..where(
-          (t) =>
-              t.idListino.equals(idListino) &
-              t.idArticolo.equals(idArticolo) &
-              (t.attivo.equals(1)),
-        ))
+    return (db.select(db.listiniArticolo)
+          ..where(
+            (t) =>
+                t.idListino.equals(idListino) &
+                t.idArticolo.equals(idArticolo) &
+                (t.attivo.equals(1)) &
+                t.validoDa.isSmallerOrEqualValue(date) &
+                t.validoA.isBiggerOrEqualValue(date),
+          )
+          ..orderBy([
+            (t) =>
+                OrderingTerm(expression: t.validoDa, mode: OrderingMode.desc),
+          ])
+          ..limit(1))
         .getSingleOrNull();
   }
 
@@ -120,66 +125,137 @@ class ArticoliDao {
         .get();
   }
 
-  Future<List<Articolo>> getArticoliPerListinoAttivo(String dataOrdine) async {
-    final q =
-        db.select(db.articoli).join([
-            innerJoin(
-              db.listiniArticolo,
-              db.listiniArticolo.idArticolo.equalsExp(db.articoli.idArticolo),
-            ),
-          ])
-          ..where(
-            db.listiniArticolo.attivo.equals(1) &
-                db.listiniArticolo.validoDa.isSmallerOrEqualValue(dataOrdine) &
-                db.listiniArticolo.validoA.isBiggerOrEqualValue(dataOrdine) &
-                db.articoli.attivo.equals(1) &
-                db.articoli.gruppo.isNotNull() &
-                db.articoli.gruppo.equals('*').not() &
-                db.articoli.categoria.isNotNull() &
-                db.articoli.categoria.equals('PPP').not() &
-                db.articoli.descrizione.isNotNull() &
-                db.articoli.descrizione.like('%.ANNULLATO%').not(),
-          )
-          ..orderBy([OrderingTerm(expression: db.articoli.descrizione)]);
-
-    final results = await q.get();
-    final seen = <int>{};
-    final items = <Articolo>[];
-    for (final row in results) {
-      final art = row.readTable(db.articoli);
-      if (seen.add(art.idArticolo)) items.add(art);
+  Future<List<Articolo>> getArticoliPerListinoAttivo(
+    String dataOrdine,
+    String filtro,
+  ) async {
+    final words = filtro.split(' ').where((w) => w.isNotEmpty).toList();
+
+    final buffer = StringBuffer('''
+      SELECT DISTINCT a.*
+      FROM "Articoli" a
+      JOIN "Listini_articolo" la ON la."idArticolo" = a."idArticolo"
+      JOIN "Listini" l ON l."idListino" = la."idListino"
+      WHERE la."attivo" = 1
+        AND la."validoDa" <= ?
+        AND la."validoA" >= ?
+        AND l."codListino" NOT IN ('OF2', 'OF4', 'I0')
+    ''');
+
+    final variables = <Variable>[
+      Variable.withString(dataOrdine),
+      Variable.withString(dataOrdine),
+    ];
+
+    if (words.isNotEmpty) {
+      buffer.write(' AND (');
+      final filters = <String>[];
+      for (final word in words) {
+        filters.add(
+          '(a."codArticolo" LIKE ? OR a."descrizione" LIKE ? OR a."ean" LIKE ?)',
+        );
+        final pattern = '%$word%';
+        variables.addAll([
+          Variable.withString(pattern),
+          Variable.withString(pattern),
+          Variable.withString(pattern),
+        ]);
+      }
+      buffer.write(filters.join(' AND '));
+      buffer.write(')');
     }
-    return items;
+
+    buffer.write(' ORDER BY a."descrizione" ASC');
+
+    final rows = await db
+        .customSelect(
+          buffer.toString(),
+          variables: variables,
+        )
+        .get();
+
+    return rows.map((row) => Articolo.fromJson(row.data)).toList();
+  }
+
+  Future<List<Listino>> getListiniArticoloValidi(
+    int idArticolo,
+    String dataOrdine,
+  ) {
+    final sql = "select distinct l.* from Listini l "
+        "join Listini_articolo la on la.idListino = l.idListino "
+        "where la.idArticolo = ? and la.attivo = 1 and "
+        "la.validoDa <= ? and la.validoA >= ? and "
+        "l.codListino not in ('OF2', 'OF4', 'I0') "
+        " order by l.codListino";
+
+    final rows = db.customSelect(
+      sql,
+      variables: [
+        Variable.withInt(idArticolo),
+        Variable.withString(dataOrdine),
+        Variable.withString(dataOrdine),
+      ],
+    );
+
+    return rows.map((row) => Listino.fromJson(row.data)).get();
+
+/*
+    return (db.select(
+      db.listiniArticolo,
+    )..where(
+            (t) =>
+                t.idArticolo.equals(idArticolo) &
+                t.attivo.equals(1) &
+                t.validoDa.isSmallerOrEqualValue(dataOrdine) &
+                t.validoA.isBiggerOrEqualValue(dataOrdine),
+          ))
+        .get();
+        */
   }
 
   Future<List<Articolo>> getArticoliPerListinoCliente(
     int idCliente,
     String dataOrdine,
   ) async {
-    final q =
-        db.select(db.articoli).join([
-            innerJoin(
-              db.listiniCliente,
-              db.listiniCliente.idArticolo.equalsExp(db.articoli.idArticolo),
-            ),
-          ])
+    final lcRows = await (db.select(db.listiniCliente)
           ..where(
-            db.listiniCliente.idCliente.equals(idCliente) &
-                db.listiniCliente.attivo.equals(1) &
-                db.listiniCliente.annullato.equals('N') &
-                db.listiniCliente.validoDa.isSmallerOrEqualValue(dataOrdine) &
-                db.listiniCliente.validoA.isBiggerOrEqualValue(dataOrdine) &
-                db.articoli.attivo.equals(1),
+            (t) =>
+                t.idCliente.equals(idCliente) &
+                t.attivo.equals(1) &
+                t.annullato.equals('N') &
+                t.validoDa.isSmallerOrEqualValue(dataOrdine) &
+                t.validoA.isBiggerOrEqualValue(dataOrdine),
+          ))
+        .get();
+
+    final validIds =
+        lcRows.map((r) => r.idArticolo).whereType<int>().toSet().toList();
+
+    if (validIds.isEmpty) return [];
+
+    return (db.select(db.articoli)
+          ..where(
+            (t) => t.idArticolo.isIn(validIds) & t.attivo.equals(1),
           )
-          ..orderBy([OrderingTerm(expression: db.articoli.descrizione)]);
-
-    final results = await q.get();
-    final seen = <int>{};
-    final items = <Articolo>[];
-    for (final row in results) {
-      final art = row.readTable(db.articoli);
-      if (seen.add(art.idArticolo)) items.add(art);
-    }
-    return items;
+          ..orderBy([(t) => OrderingTerm(expression: t.descrizione)]))
+        .get();
+  }
+
+  Future<List<ListinoCliente>> getListiniClienteValidi(
+    int idCliente,
+    int idArticolo,
+    String dataOrdine,
+  ) {
+    return (db.select(db.listiniCliente)
+          ..where(
+            (t) =>
+                t.idCliente.equals(idCliente) &
+                t.idArticolo.equals(idArticolo) &
+                t.attivo.equals(1) &
+                t.annullato.equals('N') &
+                t.validoDa.isSmallerOrEqualValue(dataOrdine) &
+                t.validoA.isBiggerOrEqualValue(dataOrdine),
+          ))
+        .get();
   }
 }

+ 62 - 47
lib/database/daos/ordini_dao.dart

@@ -8,13 +8,15 @@ class OrdiniDao {
   Future<Ordine?> getById(int id) {
     return (db.select(
       db.ordini,
-    )..where((t) => t.idOrdine.equals(id))).getSingleOrNull();
+    )..where((t) => t.idOrdine.equals(id)))
+        .getSingleOrNull();
   }
 
   Future<Ordine?> getByNumber(int numeroOrdine) {
     return (db.select(
       db.ordini,
-    )..where((t) => t.numeroOrdine.equals(numeroOrdine))).getSingleOrNull();
+    )..where((t) => t.numeroOrdine.equals(numeroOrdine)))
+        .getSingleOrNull();
   }
 
   Future<List<Ordine>> customerOrders(int idCliente, bool includeReferences) {
@@ -80,46 +82,49 @@ class OrdiniDao {
   Future<int> countNewOrdersForAgent(int idAgente) async {
     final rows = await (db.select(
       db.vOrdini,
-    )..where((t) => t.idAgente.equals(idAgente) & t.stato.equals('N'))).get();
+    )..where((t) => t.idAgente.equals(idAgente) & t.stato.equals('N')))
+        .get();
     return rows.length;
   }
 
   Future<int> countOrdersForAgent(int idAgente) async {
     final rows = await (db.select(
       db.vOrdini,
-    )..where((t) => t.idAgente.equals(idAgente))).get();
+    )..where((t) => t.idAgente.equals(idAgente)))
+        .get();
     return rows.length;
   }
 
   Future<double> getMonthlyVolume(int idAgente) async {
     final now = DateTime.now();
     final firstOfMonth = DateTime(now.year, now.month, 1);
-    final dateStr =
-        '${firstOfMonth.year.toString().padLeft(4, '0')}-'
+    final dateStr = '${firstOfMonth.year.toString().padLeft(4, '0')}-'
         '${firstOfMonth.month.toString().padLeft(2, '0')}-01';
 
-    final rows =
-        await (db.select(db.vOrdini)..where(
-              (t) =>
-                  t.idAgente.equals(idAgente) &
-                  t.dataOrdine.isNotNull() &
-                  t.dataOrdine.isBiggerOrEqualValue(dateStr),
-            ))
-            .get();
+    final rows = await (db.select(db.vOrdini)
+          ..where(
+            (t) =>
+                t.idAgente.equals(idAgente) &
+                t.dataOrdine.isNotNull() &
+                t.dataOrdine.isBiggerOrEqualValue(dateStr),
+          ))
+        .get();
     return rows.fold<double>(0, (sum, r) => sum + (r.totale ?? 0));
   }
 
   Future<int> countNewOrders() async {
     final rows = await (db.select(
       db.ordini,
-    )..where((t) => t.stato.equals('N'))).get();
+    )..where((t) => t.stato.equals('N')))
+        .get();
     return rows.length;
   }
 
   Future<int> countOrdersOnHold() async {
     final rows = await (db.select(
       db.ordini,
-    )..where((t) => t.stato.equals('H'))).get();
+    )..where((t) => t.stato.equals('H')))
+        .get();
     return rows.length;
   }
 
@@ -135,17 +140,16 @@ class OrdiniDao {
   }
 
   Future<int> generaNumeroOrdine(int idAgente) async {
-    final result =
-        await (db.select(db.ordini)
-              ..where((t) => t.idAgente.equals(idAgente))
-              ..orderBy([
-                (t) => OrderingTerm(
+    final result = await (db.select(db.ordini)
+          ..where((t) => t.idAgente.equals(idAgente))
+          ..orderBy([
+            (t) => OrderingTerm(
                   expression: t.numeroOrdine,
                   mode: OrderingMode.desc,
                 ),
-              ])
-              ..limit(1))
-            .getSingleOrNull();
+          ])
+          ..limit(1))
+        .getSingleOrNull();
     return (result?.numeroOrdine ?? 0) + 1;
   }
 
@@ -160,7 +164,8 @@ class OrdiniDao {
   }
 
   Future<int> insertOrder(Ordine order) {
-    return db.into(db.ordini).insert(order);
+    return db.into(db.ordini).insert(
+        order.toCompanion(true).copyWith(idOrdine: const Value.absent()));
   }
 
   Future<bool> updateOrder(Ordine order) {
@@ -170,66 +175,76 @@ class OrdiniDao {
   Future<void> deleteOrder(int idOrdine) {
     return (db.delete(
       db.ordini,
-    )..where((t) => t.idOrdine.equals(idOrdine))).go();
+    )..where((t) => t.idOrdine.equals(idOrdine)))
+        .go();
   }
 
   Future<int> insertOrderLine(RigaOrdine line) {
-    return db.into(db.righeOrdine).insert(line);
+    return db.into(db.righeOrdine).insert(line.toCompanion(true).copyWith(
+          idRigaOrdine: const Value.absent(),
+        ));
   }
 
   Future<bool> updateOrderLine(RigaOrdine line) {
-    return db.update(db.righeOrdine).replace(line);
+    return db.update(db.righeOrdine).replace(line.toCompanion(true).copyWith(
+          idRigaOrdine: const Value.absent(),
+        ));
   }
 
   Future<List<RigaOrdine>> getOrderLines(int idOrdine) {
     return (db.select(
       db.righeOrdine,
-    )..where((t) => t.idOrdine.equals(idOrdine))).get();
+    )..where((t) => t.idOrdine.equals(idOrdine)))
+        .get();
   }
 
   Future<void> deleteOrderLine(int idRigaOrdine) {
     return (db.delete(
       db.righeOrdine,
-    )..where((t) => t.idRigaOrdine.equals(idRigaOrdine))).go();
+    )..where((t) => t.idRigaOrdine.equals(idRigaOrdine)))
+        .go();
   }
 
   Future<void> zapOrderLines(int idOrdine) {
     return (db.delete(
       db.righeOrdine,
-    )..where((t) => t.idOrdine.equals(idOrdine))).go();
+    )..where((t) => t.idOrdine.equals(idOrdine)))
+        .go();
   }
 
   Future<Ordine?> findByNumeroOrdineSede(String numSede) {
     return (db.select(
       db.ordini,
-    )..where((t) => t.numeroOrdineSede.equals(numSede))).getSingleOrNull();
+    )..where((t) => t.numeroOrdineSede.equals(numSede)))
+        .getSingleOrNull();
   }
 
   Future<Ordine?> findByIdOrdineSede(int idSede) {
     return (db.select(
       db.ordini,
-    )..where((t) => t.idOrdineSede.equals(idSede))).getSingleOrNull();
+    )..where((t) => t.idOrdineSede.equals(idSede)))
+        .getSingleOrNull();
   }
 
   Future<double> totalAmountItems(int idOrdine) async {
-    final lines =
-        await (db.select(db.righeOrdine)..where(
-              (t) =>
-                  t.idOrdine.equals(idOrdine) &
-                  (t.rigaPromozione.equals(0) | t.rigaPromozione.isNull()),
-            ))
-            .get();
+    final lines = await (db.select(db.righeOrdine)
+          ..where(
+            (t) =>
+                t.idOrdine.equals(idOrdine) &
+                (t.rigaPromozione.equals(0) | t.rigaPromozione.isNull()),
+          ))
+        .get();
     return lines.fold<double>(0.0, (sum, l) => sum + (l.totale ?? 0.0));
   }
 
   Future<double> totalQtyItems(int idOrdine) async {
-    final lines =
-        await (db.select(db.righeOrdine)..where(
-              (t) =>
-                  t.idOrdine.equals(idOrdine) &
-                  (t.rigaPromozione.equals(0) | t.rigaPromozione.isNull()),
-            ))
-            .get();
+    final lines = await (db.select(db.righeOrdine)
+          ..where(
+            (t) =>
+                t.idOrdine.equals(idOrdine) &
+                (t.rigaPromozione.equals(0) | t.rigaPromozione.isNull()),
+          ))
+        .get();
     return lines.fold<double>(0.0, (sum, l) => sum + (l.quantita ?? 0.0));
   }
 }

Файловите разлики са ограничени, защото са твърде много
+ 409 - 725
lib/database/database.g.dart


+ 18 - 2
lib/database/db_connector_web.dart

@@ -1,8 +1,24 @@
 import 'package:drift/drift.dart';
-import 'package:drift/web.dart';
+import 'package:drift/wasm.dart';
+import 'package:flutter/services.dart' show rootBundle;
+import '../core/constants.dart';
 
 QueryExecutor createDb(String dirPath, String dbName) {
-  return WebDatabase(dbName);
+  return DatabaseConnection.delayed(
+    Future(() async {
+      final result = await WasmDatabase.open(
+        databaseName: AppConstants.kDbName,
+        sqlite3Uri: Uri.parse('sqlite3.wasm'),
+        driftWorkerUri: Uri.parse('drift_worker.js'),
+        initializeDatabase: () async {
+          final data = await rootBundle.load(AppConstants.kAssetsDbPath);
+          return data.buffer.asUint8List();
+        },
+       
+      );
+      return result.resolvedExecutor;
+    }),
+  );
 }
 
 Future<String> ensureDbReady() async {

+ 64 - 0
lib/database/sql_logger.dart

@@ -0,0 +1,64 @@
+import 'package:drift/drift.dart';
+
+class SqlLoggingInterceptor extends QueryInterceptor {
+  @override
+  Future<List<Map<String, Object?>>> runSelect(
+    QueryExecutor executor,
+    String statement,
+    List<Object?> args,
+  ) {
+    print('[Drift] SELECT: $statement | args: $args');
+    return executor.runSelect(statement, args);
+  }
+
+  @override
+  Future<int> runInsert(
+    QueryExecutor executor,
+    String statement,
+    List<Object?> args,
+  ) {
+    print('[Drift] INSERT: $statement | args: $args');
+    return executor.runInsert(statement, args);
+  }
+
+  @override
+  Future<int> runUpdate(
+    QueryExecutor executor,
+    String statement,
+    List<Object?> args,
+  ) {
+    print('[Drift] UPDATE: $statement | args: $args');
+    return executor.runUpdate(statement, args);
+  }
+
+  @override
+  Future<int> runDelete(
+    QueryExecutor executor,
+    String statement,
+    List<Object?> args,
+  ) {
+    print('[Drift] DELETE: $statement | args: $args');
+    return executor.runDelete(statement, args);
+  }
+
+  @override
+  Future<void> runCustom(
+    QueryExecutor executor,
+    String statement,
+    List<Object?> args,
+  ) {
+    print('[Drift] CUSTOM: $statement | args: $args');
+    return executor.runCustom(statement, args);
+  }
+
+  @override
+  Future<void> runBatched(
+    QueryExecutor executor,
+    BatchedStatements statements,
+  ) {
+    for (final s in statements.statements) {
+      print('[Drift] BATCHED: $s');
+    }
+    return executor.runBatched(statements);
+  }
+}

+ 2 - 2
lib/database/tables/agenti.dart

@@ -3,7 +3,7 @@ import 'package:drift/drift.dart';
 @DataClassName('Agente')
 class Agenti extends Table {
   IntColumn get idAgente => integer().named('idAgente').autoIncrement()();
-  TextColumn get codAgente => text().named('codAgente')();
+  TextColumn get codAgente => text().named('codAgente').nullable()();
   TextColumn get ragioneSociale => text().named('ragioneSociale').nullable()();
   TextColumn get pwd => text().named('pwd').nullable()();
   TextColumn get telefono => text().named('telefono').nullable()();
@@ -15,7 +15,7 @@ class Agenti extends Table {
   TextColumn get provincia => text().named('provincia').nullable()();
   TextColumn get frazione => text().named('frazione').nullable()();
   IntColumn get attivo =>
-      integer().named('attivo').withDefault(const Constant(1))();
+      integer().named('attivo').nullable().withDefault(const Constant(1))();
   IntColumn get modificaPrezzi =>
       integer().named('modificaPrezzi').nullable()();
   TextColumn get capoArea => text().named('capoArea').nullable()();

+ 19 - 0
lib/database/views/v_ordini.dart

@@ -1,5 +1,24 @@
 import 'package:drift/drift.dart';
 
+/*
+create view v_ordini as 
+select
+a.codAgente,
+c.codCliente, c.ragioneSociale,
+f.codFiliale as codFiliale,
+f.descrizione as Filiale,
+case when o.idFiliale is null then c.indirizzo else f.indirizzo end as indirizzo,
+case when o.idFiliale is null then c.citta else f.citta end as citta,
+case when o.idFiliale is null then c.provincia else f.provincia end as provincia,
+case when o.idFiliale is null then c.cap else f.cap end as cap,
+o.*,
+(select sum(totale) from righe_ordine where idOrdine=o.idordine) as totale
+from ordini o
+join agenti a on a.idagente = o.idAgente 
+join clienti c on c.idCliente  = o.idCliente
+left join filiali f on f.idfiliale = o.idfiliale
+
+*/
 class VOrdini extends Table {
   TextColumn get codAgente => text().named('codAgente')();
   TextColumn get codCliente => text().named('codCliente').nullable()();

Файловите разлики са ограничени, защото са твърде много
+ 477 - 490
lib/dto/sync_customer_dto.freezed.dart


+ 98 - 98
lib/dto/sync_customer_dto.g.dart

@@ -7,84 +7,84 @@ part of 'sync_customer_dto.dart';
 // **************************************************************************
 
 _$SyncCustomerDtoImpl _$$SyncCustomerDtoImplFromJson(
-  Map<String, dynamic> json,
-) => _$SyncCustomerDtoImpl(
-  idCliente: (json['idCliente'] as num?)?.toInt(),
-  idClienteRemoto: (json['idClienteRemoto'] as num?)?.toInt(),
-  codCliente: json['codCliente'] as String?,
-  ragioneSociale: json['ragioneSociale'] as String?,
-  indirizzo: json['indirizzo'] as String?,
-  citta: json['citta'] as String?,
-  provincia: json['provincia'] as String?,
-  cap: json['cap'] as String?,
-  frazione: json['frazione'] as String?,
-  nazione: json['nazione'] as String?,
-  telefono: json['telefono'] as String?,
-  cellulare: json['cellulare'] as String?,
-  fax: json['fax'] as String?,
-  email: json['email'] as String?,
-  partitaIva: json['partitaIva'] as String?,
-  codiceFiscale: json['codiceFiscale'] as String?,
-  note: json['note'] as String?,
-  noteOrdine: json['noteOrdine'] as String?,
-  stato: json['stato'] as String?,
-  attivo: (json['attivo'] as num?)?.toInt(),
-  idPagamento: (json['idPagamento'] as num?)?.toInt(),
-  idListino: (json['idListino'] as num?)?.toInt(),
-  dataModifica: json['dataModifica'] as String?,
-  sdi: json['sdi'] as String?,
-  pec: json['pec'] as String?,
-  altreNote: json['altreNote'] as String?,
-  numeroCivico: json['numeroCivico'] as String?,
-  pod: json['pod'] as String?,
-  pdr: json['pdr'] as String?,
-  ubicazione: json['ubicazione'] as String?,
-  banca: json['banca'] as String?,
-  bancaFiliale: json['bancaFiliale'] as String?,
-  codBlocco: json['codBlocco'] as String?,
-  creditScoreColor: json['creditScoreColor'] as String?,
-  dataCreditScore: json['dataCreditScore'] as String?,
-);
+        Map<String, dynamic> json) =>
+    _$SyncCustomerDtoImpl(
+      idCliente: (json['idCliente'] as num?)?.toInt(),
+      idClienteRemoto: (json['idClienteRemoto'] as num?)?.toInt(),
+      codCliente: json['codCliente'] as String?,
+      ragioneSociale: json['ragioneSociale'] as String?,
+      indirizzo: json['indirizzo'] as String?,
+      citta: json['citta'] as String?,
+      provincia: json['provincia'] as String?,
+      cap: json['cap'] as String?,
+      frazione: json['frazione'] as String?,
+      nazione: json['nazione'] as String?,
+      telefono: json['telefono'] as String?,
+      cellulare: json['cellulare'] as String?,
+      fax: json['fax'] as String?,
+      email: json['email'] as String?,
+      partitaIva: json['partitaIva'] as String?,
+      codiceFiscale: json['codiceFiscale'] as String?,
+      note: json['note'] as String?,
+      noteOrdine: json['noteOrdine'] as String?,
+      stato: json['stato'] as String?,
+      attivo: (json['attivo'] as num?)?.toInt(),
+      idPagamento: (json['idPagamento'] as num?)?.toInt(),
+      idListino: (json['idListino'] as num?)?.toInt(),
+      dataModifica: json['dataModifica'] as String?,
+      sdi: json['sdi'] as String?,
+      pec: json['pec'] as String?,
+      altreNote: json['altreNote'] as String?,
+      numeroCivico: json['numeroCivico'] as String?,
+      pod: json['pod'] as String?,
+      pdr: json['pdr'] as String?,
+      ubicazione: json['ubicazione'] as String?,
+      banca: json['banca'] as String?,
+      bancaFiliale: json['bancaFiliale'] as String?,
+      codBlocco: json['codBlocco'] as String?,
+      creditScoreColor: json['creditScoreColor'] as String?,
+      dataCreditScore: json['dataCreditScore'] as String?,
+    );
 
 Map<String, dynamic> _$$SyncCustomerDtoImplToJson(
-  _$SyncCustomerDtoImpl instance,
-) => <String, dynamic>{
-  'idCliente': instance.idCliente,
-  'idClienteRemoto': instance.idClienteRemoto,
-  'codCliente': instance.codCliente,
-  'ragioneSociale': instance.ragioneSociale,
-  'indirizzo': instance.indirizzo,
-  'citta': instance.citta,
-  'provincia': instance.provincia,
-  'cap': instance.cap,
-  'frazione': instance.frazione,
-  'nazione': instance.nazione,
-  'telefono': instance.telefono,
-  'cellulare': instance.cellulare,
-  'fax': instance.fax,
-  'email': instance.email,
-  'partitaIva': instance.partitaIva,
-  'codiceFiscale': instance.codiceFiscale,
-  'note': instance.note,
-  'noteOrdine': instance.noteOrdine,
-  'stato': instance.stato,
-  'attivo': instance.attivo,
-  'idPagamento': instance.idPagamento,
-  'idListino': instance.idListino,
-  'dataModifica': instance.dataModifica,
-  'sdi': instance.sdi,
-  'pec': instance.pec,
-  'altreNote': instance.altreNote,
-  'numeroCivico': instance.numeroCivico,
-  'pod': instance.pod,
-  'pdr': instance.pdr,
-  'ubicazione': instance.ubicazione,
-  'banca': instance.banca,
-  'bancaFiliale': instance.bancaFiliale,
-  'codBlocco': instance.codBlocco,
-  'creditScoreColor': instance.creditScoreColor,
-  'dataCreditScore': instance.dataCreditScore,
-};
+        _$SyncCustomerDtoImpl instance) =>
+    <String, dynamic>{
+      'idCliente': instance.idCliente,
+      'idClienteRemoto': instance.idClienteRemoto,
+      'codCliente': instance.codCliente,
+      'ragioneSociale': instance.ragioneSociale,
+      'indirizzo': instance.indirizzo,
+      'citta': instance.citta,
+      'provincia': instance.provincia,
+      'cap': instance.cap,
+      'frazione': instance.frazione,
+      'nazione': instance.nazione,
+      'telefono': instance.telefono,
+      'cellulare': instance.cellulare,
+      'fax': instance.fax,
+      'email': instance.email,
+      'partitaIva': instance.partitaIva,
+      'codiceFiscale': instance.codiceFiscale,
+      'note': instance.note,
+      'noteOrdine': instance.noteOrdine,
+      'stato': instance.stato,
+      'attivo': instance.attivo,
+      'idPagamento': instance.idPagamento,
+      'idListino': instance.idListino,
+      'dataModifica': instance.dataModifica,
+      'sdi': instance.sdi,
+      'pec': instance.pec,
+      'altreNote': instance.altreNote,
+      'numeroCivico': instance.numeroCivico,
+      'pod': instance.pod,
+      'pdr': instance.pdr,
+      'ubicazione': instance.ubicazione,
+      'banca': instance.banca,
+      'bancaFiliale': instance.bancaFiliale,
+      'codBlocco': instance.codBlocco,
+      'creditScoreColor': instance.creditScoreColor,
+      'dataCreditScore': instance.dataCreditScore,
+    };
 
 _$SyncFilialeDtoImpl _$$SyncFilialeDtoImplFromJson(Map<String, dynamic> json) =>
     _$SyncFilialeDtoImpl(
@@ -110,25 +110,25 @@ _$SyncFilialeDtoImpl _$$SyncFilialeDtoImplFromJson(Map<String, dynamic> json) =>
     );
 
 Map<String, dynamic> _$$SyncFilialeDtoImplToJson(
-  _$SyncFilialeDtoImpl instance,
-) => <String, dynamic>{
-  'idFiliale': instance.idFiliale,
-  'idFilialeRemoto': instance.idFilialeRemoto,
-  'idCliente': instance.idCliente,
-  'codCliente': instance.codCliente,
-  'codFiliale': instance.codFiliale,
-  'descrizione': instance.descrizione,
-  'indirizzo': instance.indirizzo,
-  'citta': instance.citta,
-  'provincia': instance.provincia,
-  'cap': instance.cap,
-  'frazione': instance.frazione,
-  'nazione': instance.nazione,
-  'telefono': instance.telefono,
-  'cellulare': instance.cellulare,
-  'fax': instance.fax,
-  'email': instance.email,
-  'stato': instance.stato,
-  'attivo': instance.attivo,
-  'note': instance.note,
-};
+        _$SyncFilialeDtoImpl instance) =>
+    <String, dynamic>{
+      'idFiliale': instance.idFiliale,
+      'idFilialeRemoto': instance.idFilialeRemoto,
+      'idCliente': instance.idCliente,
+      'codCliente': instance.codCliente,
+      'codFiliale': instance.codFiliale,
+      'descrizione': instance.descrizione,
+      'indirizzo': instance.indirizzo,
+      'citta': instance.citta,
+      'provincia': instance.provincia,
+      'cap': instance.cap,
+      'frazione': instance.frazione,
+      'nazione': instance.nazione,
+      'telefono': instance.telefono,
+      'cellulare': instance.cellulare,
+      'fax': instance.fax,
+      'email': instance.email,
+      'stato': instance.stato,
+      'attivo': instance.attivo,
+      'note': instance.note,
+    };

+ 2 - 2
lib/dto/sync_order_dto.dart

@@ -3,7 +3,7 @@ part 'sync_order_dto.freezed.dart';
 part 'sync_order_dto.g.dart';
 
 @freezed
-class SyncOrderDto with _$SyncOrderDto {
+sealed class SyncOrderDto with _$SyncOrderDto {
   const factory SyncOrderDto({
     int? idOrdine,
     int? idOrdineAgente,
@@ -31,7 +31,7 @@ class SyncOrderDto with _$SyncOrderDto {
 }
 
 @freezed
-class SyncOrderLineDto with _$SyncOrderLineDto {
+sealed class SyncOrderLineDto with _$SyncOrderLineDto {
   const factory SyncOrderLineDto({
     int? idRigaOrdine,
     int? idArticolo,

Файловите разлики са ограничени, защото са твърде много
+ 507 - 530
lib/dto/sync_order_dto.freezed.dart


+ 45 - 46
lib/dto/sync_order_dto.g.dart

@@ -27,8 +27,7 @@ _$SyncOrderDtoImpl _$$SyncOrderDtoImplFromJson(Map<String, dynamic> json) =>
       noteOrdine: json['noteOrdine'] as String?,
       dataRicezione: json['dataRicezione'] as String?,
       idPagamento: (json['idPagamento'] as num?)?.toInt(),
-      righeOrdine:
-          (json['righeOrdine'] as List<dynamic>?)
+      righeOrdine: (json['righeOrdine'] as List<dynamic>?)
               ?.map((e) => SyncOrderLineDto.fromJson(e as Map<String, dynamic>))
               .toList() ??
           const [],
@@ -59,49 +58,49 @@ Map<String, dynamic> _$$SyncOrderDtoImplToJson(_$SyncOrderDtoImpl instance) =>
     };
 
 _$SyncOrderLineDtoImpl _$$SyncOrderLineDtoImplFromJson(
-  Map<String, dynamic> json,
-) => _$SyncOrderLineDtoImpl(
-  idRigaOrdine: (json['idRigaOrdine'] as num?)?.toInt(),
-  idArticolo: (json['idArticolo'] as num?)?.toInt(),
-  idListino: (json['idListino'] as num?)?.toInt(),
-  quantita: (json['quantita'] as num?)?.toDouble(),
-  prezzoLordo: (json['prezzoLordo'] as num?)?.toDouble(),
-  sconto1: (json['sconto1'] as num?)?.toDouble(),
-  sconto2: (json['sconto2'] as num?)?.toDouble(),
-  sconto3: (json['sconto3'] as num?)?.toDouble(),
-  sconto4: (json['sconto4'] as num?)?.toDouble(),
-  sconto5: (json['sconto5'] as num?)?.toDouble(),
-  scontoMerce: (json['scontoMerce'] as num?)?.toDouble(),
-  prezzoNetto: (json['prezzoNetto'] as num?)?.toDouble(),
-  totale: (json['totale'] as num?)?.toDouble(),
-  dataConsegna: json['dataConsegna'] as String?,
-  qtaEvasa: (json['qtaEvasa'] as num?)?.toDouble(),
-  stato: json['stato'] as String?,
-  colli: (json['colli'] as num?)?.toDouble(),
-  promozione: (json['promozione'] as num?)?.toInt(),
-  rigaPromozione: (json['rigaPromozione'] as num?)?.toInt(),
-);
+        Map<String, dynamic> json) =>
+    _$SyncOrderLineDtoImpl(
+      idRigaOrdine: (json['idRigaOrdine'] as num?)?.toInt(),
+      idArticolo: (json['idArticolo'] as num?)?.toInt(),
+      idListino: (json['idListino'] as num?)?.toInt(),
+      quantita: (json['quantita'] as num?)?.toDouble(),
+      prezzoLordo: (json['prezzoLordo'] as num?)?.toDouble(),
+      sconto1: (json['sconto1'] as num?)?.toDouble(),
+      sconto2: (json['sconto2'] as num?)?.toDouble(),
+      sconto3: (json['sconto3'] as num?)?.toDouble(),
+      sconto4: (json['sconto4'] as num?)?.toDouble(),
+      sconto5: (json['sconto5'] as num?)?.toDouble(),
+      scontoMerce: (json['scontoMerce'] as num?)?.toDouble(),
+      prezzoNetto: (json['prezzoNetto'] as num?)?.toDouble(),
+      totale: (json['totale'] as num?)?.toDouble(),
+      dataConsegna: json['dataConsegna'] as String?,
+      qtaEvasa: (json['qtaEvasa'] as num?)?.toDouble(),
+      stato: json['stato'] as String?,
+      colli: (json['colli'] as num?)?.toDouble(),
+      promozione: (json['promozione'] as num?)?.toInt(),
+      rigaPromozione: (json['rigaPromozione'] as num?)?.toInt(),
+    );
 
 Map<String, dynamic> _$$SyncOrderLineDtoImplToJson(
-  _$SyncOrderLineDtoImpl instance,
-) => <String, dynamic>{
-  'idRigaOrdine': instance.idRigaOrdine,
-  'idArticolo': instance.idArticolo,
-  'idListino': instance.idListino,
-  'quantita': instance.quantita,
-  'prezzoLordo': instance.prezzoLordo,
-  'sconto1': instance.sconto1,
-  'sconto2': instance.sconto2,
-  'sconto3': instance.sconto3,
-  'sconto4': instance.sconto4,
-  'sconto5': instance.sconto5,
-  'scontoMerce': instance.scontoMerce,
-  'prezzoNetto': instance.prezzoNetto,
-  'totale': instance.totale,
-  'dataConsegna': instance.dataConsegna,
-  'qtaEvasa': instance.qtaEvasa,
-  'stato': instance.stato,
-  'colli': instance.colli,
-  'promozione': instance.promozione,
-  'rigaPromozione': instance.rigaPromozione,
-};
+        _$SyncOrderLineDtoImpl instance) =>
+    <String, dynamic>{
+      'idRigaOrdine': instance.idRigaOrdine,
+      'idArticolo': instance.idArticolo,
+      'idListino': instance.idListino,
+      'quantita': instance.quantita,
+      'prezzoLordo': instance.prezzoLordo,
+      'sconto1': instance.sconto1,
+      'sconto2': instance.sconto2,
+      'sconto3': instance.sconto3,
+      'sconto4': instance.sconto4,
+      'sconto5': instance.sconto5,
+      'scontoMerce': instance.scontoMerce,
+      'prezzoNetto': instance.prezzoNetto,
+      'totale': instance.totale,
+      'dataConsegna': instance.dataConsegna,
+      'qtaEvasa': instance.qtaEvasa,
+      'stato': instance.stato,
+      'colli': instance.colli,
+      'promozione': instance.promozione,
+      'rigaPromozione': instance.rigaPromozione,
+    };

+ 10 - 10
lib/dto/sync_promo_dto.dart

@@ -3,7 +3,7 @@ part 'sync_promo_dto.freezed.dart';
 part 'sync_promo_dto.g.dart';
 
 @freezed
-class SyncPromoDto with _$SyncPromoDto {
+sealed class SyncPromoDto with _$SyncPromoDto {
   const factory SyncPromoDto({
     int? progressivo,
     int? tipoPromozione,
@@ -29,7 +29,7 @@ class SyncPromoDto with _$SyncPromoDto {
 }
 
 @freezed
-class SyncPromoArtDto with _$SyncPromoArtDto {
+sealed class SyncPromoArtDto with _$SyncPromoArtDto {
   const factory SyncPromoArtDto({
     PromoArticoloKey? promozioneArticoloKey,
     ItemDto? articolo,
@@ -39,7 +39,7 @@ class SyncPromoArtDto with _$SyncPromoArtDto {
 }
 
 @freezed
-class SyncPromoListiniDto with _$SyncPromoListiniDto {
+sealed class SyncPromoListiniDto with _$SyncPromoListiniDto {
   const factory SyncPromoListiniDto({
     PromoListinoKey? promozioneListinoKey,
   }) = _SyncPromoListiniDto;
@@ -48,7 +48,7 @@ class SyncPromoListiniDto with _$SyncPromoListiniDto {
 }
 
 @freezed
-class SyncPromoOmaggioDto with _$SyncPromoOmaggioDto {
+sealed class SyncPromoOmaggioDto with _$SyncPromoOmaggioDto {
   const factory SyncPromoOmaggioDto({
     PromoOmaggioKey? promozioneOmaggioKey,
     ItemDto? articolo,
@@ -59,7 +59,7 @@ class SyncPromoOmaggioDto with _$SyncPromoOmaggioDto {
 }
 
 @freezed
-class PromoArticoloKey with _$PromoArticoloKey {
+sealed class PromoArticoloKey with _$PromoArticoloKey {
   const factory PromoArticoloKey({
     int? progressivo,
     int? riga,
@@ -69,7 +69,7 @@ class PromoArticoloKey with _$PromoArticoloKey {
 }
 
 @freezed
-class PromoListinoKey with _$PromoListinoKey {
+sealed class PromoListinoKey with _$PromoListinoKey {
   const factory PromoListinoKey({
     int? progressivo,
     int? idListino,
@@ -79,7 +79,7 @@ class PromoListinoKey with _$PromoListinoKey {
 }
 
 @freezed
-class PromoOmaggioKey with _$PromoOmaggioKey {
+sealed class PromoOmaggioKey with _$PromoOmaggioKey {
   const factory PromoOmaggioKey({
     int? progressivo,
     int? riga,
@@ -89,7 +89,7 @@ class PromoOmaggioKey with _$PromoOmaggioKey {
 }
 
 @freezed
-class ItemDto with _$ItemDto {
+sealed class ItemDto with _$ItemDto {
   const factory ItemDto({
     int? idArticolo,
     String? codArticolo,
@@ -103,7 +103,7 @@ class ItemDto with _$ItemDto {
 }
 
 @freezed
-class ListinoDto with _$ListinoDto {
+sealed class ListinoDto with _$ListinoDto {
   const factory ListinoDto({
     int? idListino,
     String? codListino,
@@ -114,7 +114,7 @@ class ListinoDto with _$ListinoDto {
 }
 
 @freezed
-class ScontoDto with _$ScontoDto {
+sealed class ScontoDto with _$ScontoDto {
   const factory ScontoDto({
     int? idSconto,
     String? codSconto,

Файловите разлики са ограничени, защото са твърде много
+ 381 - 427
lib/dto/sync_promo_dto.freezed.dart


+ 113 - 116
lib/dto/sync_promo_dto.g.dart

@@ -6,48 +6,46 @@ part of 'sync_promo_dto.dart';
 // JsonSerializableGenerator
 // **************************************************************************
 
-_$SyncPromoDtoImpl _$$SyncPromoDtoImplFromJson(
-  Map<String, dynamic> json,
-) => _$SyncPromoDtoImpl(
-  progressivo: (json['progressivo'] as num?)?.toInt(),
-  tipoPromozione: (json['tipoPromozione'] as num?)?.toInt(),
-  descrizione: json['descrizione'] as String?,
-  dataInizio: json['dataInizio'] as String?,
-  dataFine: json['dataFine'] as String?,
-  limiteQta: (json['limiteQta'] as num?)?.toDouble(),
-  quantitaOmaggio: (json['quantitaOmaggio'] as num?)?.toDouble(),
-  limiteImporto: (json['limiteImporto'] as num?)?.toDouble(),
-  stato: json['stato'] as String?,
-  priorita: (json['priorita'] as num?)?.toInt(),
-  codice: (json['codice'] as num?)?.toInt(),
-  articoloPromo: json['articoloPromo'] == null
-      ? null
-      : ItemDto.fromJson(json['articoloPromo'] as Map<String, dynamic>),
-  articoloOmaggio: json['articoloOmaggio'] == null
-      ? null
-      : ItemDto.fromJson(json['articoloOmaggio'] as Map<String, dynamic>),
-  listinoOmaggio: json['listinoOmaggio'] == null
-      ? null
-      : ListinoDto.fromJson(json['listinoOmaggio'] as Map<String, dynamic>),
-  sconto: json['sconto'] == null
-      ? null
-      : ScontoDto.fromJson(json['sconto'] as Map<String, dynamic>),
-  promozioneArticoli:
-      (json['promozioneArticoli'] as List<dynamic>?)
-          ?.map((e) => SyncPromoArtDto.fromJson(e as Map<String, dynamic>))
-          .toList() ??
-      const [],
-  promozioneListini:
-      (json['promozioneListini'] as List<dynamic>?)
-          ?.map((e) => SyncPromoListiniDto.fromJson(e as Map<String, dynamic>))
-          .toList() ??
-      const [],
-  promozioneOmaggio:
-      (json['promozioneOmaggio'] as List<dynamic>?)
-          ?.map((e) => SyncPromoOmaggioDto.fromJson(e as Map<String, dynamic>))
-          .toList() ??
-      const [],
-);
+_$SyncPromoDtoImpl _$$SyncPromoDtoImplFromJson(Map<String, dynamic> json) =>
+    _$SyncPromoDtoImpl(
+      progressivo: (json['progressivo'] as num?)?.toInt(),
+      tipoPromozione: (json['tipoPromozione'] as num?)?.toInt(),
+      descrizione: json['descrizione'] as String?,
+      dataInizio: json['dataInizio'] as String?,
+      dataFine: json['dataFine'] as String?,
+      limiteQta: (json['limiteQta'] as num?)?.toDouble(),
+      quantitaOmaggio: (json['quantitaOmaggio'] as num?)?.toDouble(),
+      limiteImporto: (json['limiteImporto'] as num?)?.toDouble(),
+      stato: json['stato'] as String?,
+      priorita: (json['priorita'] as num?)?.toInt(),
+      codice: (json['codice'] as num?)?.toInt(),
+      articoloPromo: json['articoloPromo'] == null
+          ? null
+          : ItemDto.fromJson(json['articoloPromo'] as Map<String, dynamic>),
+      articoloOmaggio: json['articoloOmaggio'] == null
+          ? null
+          : ItemDto.fromJson(json['articoloOmaggio'] as Map<String, dynamic>),
+      listinoOmaggio: json['listinoOmaggio'] == null
+          ? null
+          : ListinoDto.fromJson(json['listinoOmaggio'] as Map<String, dynamic>),
+      sconto: json['sconto'] == null
+          ? null
+          : ScontoDto.fromJson(json['sconto'] as Map<String, dynamic>),
+      promozioneArticoli: (json['promozioneArticoli'] as List<dynamic>?)
+              ?.map((e) => SyncPromoArtDto.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      promozioneListini: (json['promozioneListini'] as List<dynamic>?)
+              ?.map((e) =>
+                  SyncPromoListiniDto.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      promozioneOmaggio: (json['promozioneOmaggio'] as List<dynamic>?)
+              ?.map((e) =>
+                  SyncPromoOmaggioDto.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+    );
 
 Map<String, dynamic> _$$SyncPromoDtoImplToJson(_$SyncPromoDtoImpl instance) =>
     <String, dynamic>{
@@ -72,102 +70,101 @@ Map<String, dynamic> _$$SyncPromoDtoImplToJson(_$SyncPromoDtoImpl instance) =>
     };
 
 _$SyncPromoArtDtoImpl _$$SyncPromoArtDtoImplFromJson(
-  Map<String, dynamic> json,
-) => _$SyncPromoArtDtoImpl(
-  promozioneArticoloKey: json['promozioneArticoloKey'] == null
-      ? null
-      : PromoArticoloKey.fromJson(
-          json['promozioneArticoloKey'] as Map<String, dynamic>,
-        ),
-  articolo: json['articolo'] == null
-      ? null
-      : ItemDto.fromJson(json['articolo'] as Map<String, dynamic>),
-);
+        Map<String, dynamic> json) =>
+    _$SyncPromoArtDtoImpl(
+      promozioneArticoloKey: json['promozioneArticoloKey'] == null
+          ? null
+          : PromoArticoloKey.fromJson(
+              json['promozioneArticoloKey'] as Map<String, dynamic>),
+      articolo: json['articolo'] == null
+          ? null
+          : ItemDto.fromJson(json['articolo'] as Map<String, dynamic>),
+    );
 
 Map<String, dynamic> _$$SyncPromoArtDtoImplToJson(
-  _$SyncPromoArtDtoImpl instance,
-) => <String, dynamic>{
-  'promozioneArticoloKey': instance.promozioneArticoloKey,
-  'articolo': instance.articolo,
-};
+        _$SyncPromoArtDtoImpl instance) =>
+    <String, dynamic>{
+      'promozioneArticoloKey': instance.promozioneArticoloKey,
+      'articolo': instance.articolo,
+    };
 
 _$SyncPromoListiniDtoImpl _$$SyncPromoListiniDtoImplFromJson(
-  Map<String, dynamic> json,
-) => _$SyncPromoListiniDtoImpl(
-  promozioneListinoKey: json['promozioneListinoKey'] == null
-      ? null
-      : PromoListinoKey.fromJson(
-          json['promozioneListinoKey'] as Map<String, dynamic>,
-        ),
-);
+        Map<String, dynamic> json) =>
+    _$SyncPromoListiniDtoImpl(
+      promozioneListinoKey: json['promozioneListinoKey'] == null
+          ? null
+          : PromoListinoKey.fromJson(
+              json['promozioneListinoKey'] as Map<String, dynamic>),
+    );
 
 Map<String, dynamic> _$$SyncPromoListiniDtoImplToJson(
-  _$SyncPromoListiniDtoImpl instance,
-) => <String, dynamic>{'promozioneListinoKey': instance.promozioneListinoKey};
+        _$SyncPromoListiniDtoImpl instance) =>
+    <String, dynamic>{
+      'promozioneListinoKey': instance.promozioneListinoKey,
+    };
 
 _$SyncPromoOmaggioDtoImpl _$$SyncPromoOmaggioDtoImplFromJson(
-  Map<String, dynamic> json,
-) => _$SyncPromoOmaggioDtoImpl(
-  promozioneOmaggioKey: json['promozioneOmaggioKey'] == null
-      ? null
-      : PromoOmaggioKey.fromJson(
-          json['promozioneOmaggioKey'] as Map<String, dynamic>,
-        ),
-  articolo: json['articolo'] == null
-      ? null
-      : ItemDto.fromJson(json['articolo'] as Map<String, dynamic>),
-  quantita: (json['quantita'] as num?)?.toDouble(),
-);
+        Map<String, dynamic> json) =>
+    _$SyncPromoOmaggioDtoImpl(
+      promozioneOmaggioKey: json['promozioneOmaggioKey'] == null
+          ? null
+          : PromoOmaggioKey.fromJson(
+              json['promozioneOmaggioKey'] as Map<String, dynamic>),
+      articolo: json['articolo'] == null
+          ? null
+          : ItemDto.fromJson(json['articolo'] as Map<String, dynamic>),
+      quantita: (json['quantita'] as num?)?.toDouble(),
+    );
 
 Map<String, dynamic> _$$SyncPromoOmaggioDtoImplToJson(
-  _$SyncPromoOmaggioDtoImpl instance,
-) => <String, dynamic>{
-  'promozioneOmaggioKey': instance.promozioneOmaggioKey,
-  'articolo': instance.articolo,
-  'quantita': instance.quantita,
-};
+        _$SyncPromoOmaggioDtoImpl instance) =>
+    <String, dynamic>{
+      'promozioneOmaggioKey': instance.promozioneOmaggioKey,
+      'articolo': instance.articolo,
+      'quantita': instance.quantita,
+    };
 
 _$PromoArticoloKeyImpl _$$PromoArticoloKeyImplFromJson(
-  Map<String, dynamic> json,
-) => _$PromoArticoloKeyImpl(
-  progressivo: (json['progressivo'] as num?)?.toInt(),
-  riga: (json['riga'] as num?)?.toInt(),
-);
+        Map<String, dynamic> json) =>
+    _$PromoArticoloKeyImpl(
+      progressivo: (json['progressivo'] as num?)?.toInt(),
+      riga: (json['riga'] as num?)?.toInt(),
+    );
 
 Map<String, dynamic> _$$PromoArticoloKeyImplToJson(
-  _$PromoArticoloKeyImpl instance,
-) => <String, dynamic>{
-  'progressivo': instance.progressivo,
-  'riga': instance.riga,
-};
+        _$PromoArticoloKeyImpl instance) =>
+    <String, dynamic>{
+      'progressivo': instance.progressivo,
+      'riga': instance.riga,
+    };
 
 _$PromoListinoKeyImpl _$$PromoListinoKeyImplFromJson(
-  Map<String, dynamic> json,
-) => _$PromoListinoKeyImpl(
-  progressivo: (json['progressivo'] as num?)?.toInt(),
-  idListino: (json['idListino'] as num?)?.toInt(),
-);
+        Map<String, dynamic> json) =>
+    _$PromoListinoKeyImpl(
+      progressivo: (json['progressivo'] as num?)?.toInt(),
+      idListino: (json['idListino'] as num?)?.toInt(),
+    );
 
 Map<String, dynamic> _$$PromoListinoKeyImplToJson(
-  _$PromoListinoKeyImpl instance,
-) => <String, dynamic>{
-  'progressivo': instance.progressivo,
-  'idListino': instance.idListino,
-};
+        _$PromoListinoKeyImpl instance) =>
+    <String, dynamic>{
+      'progressivo': instance.progressivo,
+      'idListino': instance.idListino,
+    };
 
 _$PromoOmaggioKeyImpl _$$PromoOmaggioKeyImplFromJson(
-  Map<String, dynamic> json,
-) => _$PromoOmaggioKeyImpl(
-  progressivo: (json['progressivo'] as num?)?.toInt(),
-  riga: (json['riga'] as num?)?.toInt(),
-);
+        Map<String, dynamic> json) =>
+    _$PromoOmaggioKeyImpl(
+      progressivo: (json['progressivo'] as num?)?.toInt(),
+      riga: (json['riga'] as num?)?.toInt(),
+    );
 
 Map<String, dynamic> _$$PromoOmaggioKeyImplToJson(
-  _$PromoOmaggioKeyImpl instance,
-) => <String, dynamic>{
-  'progressivo': instance.progressivo,
-  'riga': instance.riga,
-};
+        _$PromoOmaggioKeyImpl instance) =>
+    <String, dynamic>{
+      'progressivo': instance.progressivo,
+      'riga': instance.riga,
+    };
 
 _$ItemDtoImpl _$$ItemDtoImplFromJson(Map<String, dynamic> json) =>
     _$ItemDtoImpl(

+ 1 - 1
lib/dto/sync_response_dto.dart

@@ -3,7 +3,7 @@ part 'sync_response_dto.freezed.dart';
 part 'sync_response_dto.g.dart';
 
 @freezed
-class SyncResponseDto with _$SyncResponseDto {
+sealed class SyncResponseDto with _$SyncResponseDto {
   const SyncResponseDto._();
   const factory SyncResponseDto({
     @Default(0) int status,

+ 91 - 106
lib/dto/sync_response_dto.freezed.dart

@@ -12,8 +12,7 @@ part of 'sync_response_dto.dart';
 T _$identity<T>(T value) => value;
 
 final _privateConstructorUsedError = UnsupportedError(
-  'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
-);
+    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
 
 SyncResponseDto _$SyncResponseDtoFromJson(Map<String, dynamic> json) {
   return _SyncResponseDto.fromJson(json);
@@ -40,17 +39,15 @@ mixin _$SyncResponseDto {
 /// @nodoc
 abstract class $SyncResponseDtoCopyWith<$Res> {
   factory $SyncResponseDtoCopyWith(
-    SyncResponseDto value,
-    $Res Function(SyncResponseDto) then,
-  ) = _$SyncResponseDtoCopyWithImpl<$Res, SyncResponseDto>;
+          SyncResponseDto value, $Res Function(SyncResponseDto) then) =
+      _$SyncResponseDtoCopyWithImpl<$Res, SyncResponseDto>;
   @useResult
-  $Res call({
-    int status,
-    String statusText,
-    int? idAssegnato,
-    List<String> messages,
-    List<String> validationErrors,
-  });
+  $Res call(
+      {int status,
+      String statusText,
+      int? idAssegnato,
+      List<String> messages,
+      List<String> validationErrors});
 }
 
 /// @nodoc
@@ -74,50 +71,45 @@ class _$SyncResponseDtoCopyWithImpl<$Res, $Val extends SyncResponseDto>
     Object? messages = null,
     Object? validationErrors = null,
   }) {
-    return _then(
-      _value.copyWith(
-            status: null == status
-                ? _value.status
-                : status // ignore: cast_nullable_to_non_nullable
-                      as int,
-            statusText: null == statusText
-                ? _value.statusText
-                : statusText // ignore: cast_nullable_to_non_nullable
-                      as String,
-            idAssegnato: freezed == idAssegnato
-                ? _value.idAssegnato
-                : idAssegnato // ignore: cast_nullable_to_non_nullable
-                      as int?,
-            messages: null == messages
-                ? _value.messages
-                : messages // ignore: cast_nullable_to_non_nullable
-                      as List<String>,
-            validationErrors: null == validationErrors
-                ? _value.validationErrors
-                : validationErrors // ignore: cast_nullable_to_non_nullable
-                      as List<String>,
-          )
-          as $Val,
-    );
+    return _then(_value.copyWith(
+      status: null == status
+          ? _value.status
+          : status // ignore: cast_nullable_to_non_nullable
+              as int,
+      statusText: null == statusText
+          ? _value.statusText
+          : statusText // ignore: cast_nullable_to_non_nullable
+              as String,
+      idAssegnato: freezed == idAssegnato
+          ? _value.idAssegnato
+          : idAssegnato // ignore: cast_nullable_to_non_nullable
+              as int?,
+      messages: null == messages
+          ? _value.messages
+          : messages // ignore: cast_nullable_to_non_nullable
+              as List<String>,
+      validationErrors: null == validationErrors
+          ? _value.validationErrors
+          : validationErrors // ignore: cast_nullable_to_non_nullable
+              as List<String>,
+    ) as $Val);
   }
 }
 
 /// @nodoc
 abstract class _$$SyncResponseDtoImplCopyWith<$Res>
     implements $SyncResponseDtoCopyWith<$Res> {
-  factory _$$SyncResponseDtoImplCopyWith(
-    _$SyncResponseDtoImpl value,
-    $Res Function(_$SyncResponseDtoImpl) then,
-  ) = __$$SyncResponseDtoImplCopyWithImpl<$Res>;
+  factory _$$SyncResponseDtoImplCopyWith(_$SyncResponseDtoImpl value,
+          $Res Function(_$SyncResponseDtoImpl) then) =
+      __$$SyncResponseDtoImplCopyWithImpl<$Res>;
   @override
   @useResult
-  $Res call({
-    int status,
-    String statusText,
-    int? idAssegnato,
-    List<String> messages,
-    List<String> validationErrors,
-  });
+  $Res call(
+      {int status,
+      String statusText,
+      int? idAssegnato,
+      List<String> messages,
+      List<String> validationErrors});
 }
 
 /// @nodoc
@@ -125,9 +117,8 @@ class __$$SyncResponseDtoImplCopyWithImpl<$Res>
     extends _$SyncResponseDtoCopyWithImpl<$Res, _$SyncResponseDtoImpl>
     implements _$$SyncResponseDtoImplCopyWith<$Res> {
   __$$SyncResponseDtoImplCopyWithImpl(
-    _$SyncResponseDtoImpl _value,
-    $Res Function(_$SyncResponseDtoImpl) _then,
-  ) : super(_value, _then);
+      _$SyncResponseDtoImpl _value, $Res Function(_$SyncResponseDtoImpl) _then)
+      : super(_value, _then);
 
   /// Create a copy of SyncResponseDto
   /// with the given fields replaced by the non-null parameter values.
@@ -140,45 +131,43 @@ class __$$SyncResponseDtoImplCopyWithImpl<$Res>
     Object? messages = null,
     Object? validationErrors = null,
   }) {
-    return _then(
-      _$SyncResponseDtoImpl(
-        status: null == status
-            ? _value.status
-            : status // ignore: cast_nullable_to_non_nullable
-                  as int,
-        statusText: null == statusText
-            ? _value.statusText
-            : statusText // ignore: cast_nullable_to_non_nullable
-                  as String,
-        idAssegnato: freezed == idAssegnato
-            ? _value.idAssegnato
-            : idAssegnato // ignore: cast_nullable_to_non_nullable
-                  as int?,
-        messages: null == messages
-            ? _value._messages
-            : messages // ignore: cast_nullable_to_non_nullable
-                  as List<String>,
-        validationErrors: null == validationErrors
-            ? _value._validationErrors
-            : validationErrors // ignore: cast_nullable_to_non_nullable
-                  as List<String>,
-      ),
-    );
+    return _then(_$SyncResponseDtoImpl(
+      status: null == status
+          ? _value.status
+          : status // ignore: cast_nullable_to_non_nullable
+              as int,
+      statusText: null == statusText
+          ? _value.statusText
+          : statusText // ignore: cast_nullable_to_non_nullable
+              as String,
+      idAssegnato: freezed == idAssegnato
+          ? _value.idAssegnato
+          : idAssegnato // ignore: cast_nullable_to_non_nullable
+              as int?,
+      messages: null == messages
+          ? _value._messages
+          : messages // ignore: cast_nullable_to_non_nullable
+              as List<String>,
+      validationErrors: null == validationErrors
+          ? _value._validationErrors
+          : validationErrors // ignore: cast_nullable_to_non_nullable
+              as List<String>,
+    ));
   }
 }
 
 /// @nodoc
 @JsonSerializable()
 class _$SyncResponseDtoImpl extends _SyncResponseDto {
-  const _$SyncResponseDtoImpl({
-    this.status = 0,
-    this.statusText = 'OK',
-    this.idAssegnato,
-    final List<String> messages = const [],
-    final List<String> validationErrors = const [],
-  }) : _messages = messages,
-       _validationErrors = validationErrors,
-       super._();
+  const _$SyncResponseDtoImpl(
+      {this.status = 0,
+      this.statusText = 'OK',
+      this.idAssegnato,
+      final List<String> messages = const [],
+      final List<String> validationErrors = const []})
+      : _messages = messages,
+        _validationErrors = validationErrors,
+        super._();
 
   factory _$SyncResponseDtoImpl.fromJson(Map<String, dynamic> json) =>
       _$$SyncResponseDtoImplFromJson(json);
@@ -226,22 +215,19 @@ class _$SyncResponseDtoImpl extends _SyncResponseDto {
             (identical(other.idAssegnato, idAssegnato) ||
                 other.idAssegnato == idAssegnato) &&
             const DeepCollectionEquality().equals(other._messages, _messages) &&
-            const DeepCollectionEquality().equals(
-              other._validationErrors,
-              _validationErrors,
-            ));
+            const DeepCollectionEquality()
+                .equals(other._validationErrors, _validationErrors));
   }
 
   @JsonKey(includeFromJson: false, includeToJson: false)
   @override
   int get hashCode => Object.hash(
-    runtimeType,
-    status,
-    statusText,
-    idAssegnato,
-    const DeepCollectionEquality().hash(_messages),
-    const DeepCollectionEquality().hash(_validationErrors),
-  );
+      runtimeType,
+      status,
+      statusText,
+      idAssegnato,
+      const DeepCollectionEquality().hash(_messages),
+      const DeepCollectionEquality().hash(_validationErrors));
 
   /// Create a copy of SyncResponseDto
   /// with the given fields replaced by the non-null parameter values.
@@ -250,24 +236,23 @@ class _$SyncResponseDtoImpl extends _SyncResponseDto {
   @pragma('vm:prefer-inline')
   _$$SyncResponseDtoImplCopyWith<_$SyncResponseDtoImpl> get copyWith =>
       __$$SyncResponseDtoImplCopyWithImpl<_$SyncResponseDtoImpl>(
-        this,
-        _$identity,
-      );
+          this, _$identity);
 
   @override
   Map<String, dynamic> toJson() {
-    return _$$SyncResponseDtoImplToJson(this);
+    return _$$SyncResponseDtoImplToJson(
+      this,
+    );
   }
 }
 
 abstract class _SyncResponseDto extends SyncResponseDto {
-  const factory _SyncResponseDto({
-    final int status,
-    final String statusText,
-    final int? idAssegnato,
-    final List<String> messages,
-    final List<String> validationErrors,
-  }) = _$SyncResponseDtoImpl;
+  const factory _SyncResponseDto(
+      {final int status,
+      final String statusText,
+      final int? idAssegnato,
+      final List<String> messages,
+      final List<String> validationErrors}) = _$SyncResponseDtoImpl;
   const _SyncResponseDto._() : super._();
 
   factory _SyncResponseDto.fromJson(Map<String, dynamic> json) =

+ 22 - 22
lib/dto/sync_response_dto.g.dart

@@ -7,27 +7,27 @@ part of 'sync_response_dto.dart';
 // **************************************************************************
 
 _$SyncResponseDtoImpl _$$SyncResponseDtoImplFromJson(
-  Map<String, dynamic> json,
-) => _$SyncResponseDtoImpl(
-  status: (json['status'] as num?)?.toInt() ?? 0,
-  statusText: json['statusText'] as String? ?? 'OK',
-  idAssegnato: (json['idAssegnato'] as num?)?.toInt(),
-  messages:
-      (json['messages'] as List<dynamic>?)?.map((e) => e as String).toList() ??
-      const [],
-  validationErrors:
-      (json['validationErrors'] as List<dynamic>?)
-          ?.map((e) => e as String)
-          .toList() ??
-      const [],
-);
+        Map<String, dynamic> json) =>
+    _$SyncResponseDtoImpl(
+      status: (json['status'] as num?)?.toInt() ?? 0,
+      statusText: json['statusText'] as String? ?? 'OK',
+      idAssegnato: (json['idAssegnato'] as num?)?.toInt(),
+      messages: (json['messages'] as List<dynamic>?)
+              ?.map((e) => e as String)
+              .toList() ??
+          const [],
+      validationErrors: (json['validationErrors'] as List<dynamic>?)
+              ?.map((e) => e as String)
+              .toList() ??
+          const [],
+    );
 
 Map<String, dynamic> _$$SyncResponseDtoImplToJson(
-  _$SyncResponseDtoImpl instance,
-) => <String, dynamic>{
-  'status': instance.status,
-  'statusText': instance.statusText,
-  'idAssegnato': instance.idAssegnato,
-  'messages': instance.messages,
-  'validationErrors': instance.validationErrors,
-};
+        _$SyncResponseDtoImpl instance) =>
+    <String, dynamic>{
+      'status': instance.status,
+      'statusText': instance.statusText,
+      'idAssegnato': instance.idAssegnato,
+      'messages': instance.messages,
+      'validationErrors': instance.validationErrors,
+    };

+ 53 - 25
lib/providers/order_provider.dart

@@ -7,10 +7,17 @@ import 'database_provider.dart';
 class OrderState {
   final Ordine? order;
   final List<RigaOrdine> lines;
+  final List<int> removedLineIds;
   final bool isLoading;
   final String? error;
 
-  const OrderState({this.order, this.lines = const [], this.isLoading = false, this.error});
+  const OrderState({
+    this.order,
+    this.lines = const [],
+    this.removedLineIds = const [],
+    this.isLoading = false,
+    this.error,
+  });
 
   double get totale {
     return lines.fold(0.0, (sum, l) => sum + (l.totale ?? 0));
@@ -65,38 +72,59 @@ class OrderNotifier extends Notifier<OrderState> {
     );
   }
 
-  Future<void> addLine(RigaOrdine line) async {
+  void addLine(RigaOrdine line) {
     if (state.order == null) return;
-    final dao = ref.read(ordiniDaoProvider);
-    await dao.insertOrderLine(line.copyWith(idOrdine: Value<int?>(state.order!.idOrdine)));
-    final lines = await dao.getOrderLines(state.order!.idOrdine);
-    state = OrderState(order: state.order, lines: lines);
+    state = OrderState(
+      order: state.order,
+      lines: [...state.lines, line],
+      removedLineIds: state.removedLineIds,
+    );
   }
 
-  Future<void> editLine(RigaOrdine line) async {
-    if (state.order == null) return;
-    final dao = ref.read(ordiniDaoProvider);
-    await dao.updateOrderLine(line);
-    final lines = await dao.getOrderLines(state.order!.idOrdine);
-    state = OrderState(order: state.order, lines: lines);
+  void removeLine(int index) {
+    if (state.order == null || index < 0 || index >= state.lines.length) return;
+    final removed = state.lines[index];
+    final newLines = List<RigaOrdine>.from(state.lines)..removeAt(index);
+    final newRemovedIds = [
+      ...state.removedLineIds,
+      if (removed.idRigaOrdine > 0) removed.idRigaOrdine,
+    ];
+    state = OrderState(
+      order: state.order,
+      lines: newLines,
+      removedLineIds: newRemovedIds,
+    );
   }
 
-  Future<void> removeLine(int idRigaOrdine) async {
+  Future<void> saveOrder() async {
     if (state.order == null) return;
     final dao = ref.read(ordiniDaoProvider);
-    await dao.deleteOrderLine(idRigaOrdine);
-    final lines = await dao.getOrderLines(state.order!.idOrdine);
-    state = OrderState(order: state.order, lines: lines);
-  }
 
-  Future<void> saveOrder() async {
-    if (state.order == null) return;
-    final mgr = _buildManager();
-    mgr.idAgente = state.order!.idAgente ?? 0;
-    mgr.setCurrentOrder(state.order!);
-    await mgr.saveOrder();
-    final updated = mgr.currentOrder;
-    state = OrderState(order: updated, lines: state.lines);
+    Ordine order = state.order!;
+
+    if (order.idOrdine > 0) {
+      await dao.updateOrder(order);
+    } else {
+      final id = await dao.insertOrder(order);
+      order = order.copyWith(idOrdine: id);
+    }
+
+    for (final line in state.lines) {
+      final lineToSave = line.copyWith(
+        idOrdine: Value<int?>(order.idOrdine),
+      );
+      if (line.idRigaOrdine == 0) {
+        await dao.insertOrderLine(lineToSave);
+      } else {
+        await dao.updateOrderLine(lineToSave);
+      }
+    }
+
+    for (final idRiga in state.removedLineIds) {
+      await dao.deleteOrderLine(idRiga);
+    }
+
+    state = OrderState(order: order, lines: state.lines);
   }
 
   Future<void> deleteOrder() async {

+ 43 - 543
lib/ui/pages/orders/order_edit_page.dart

@@ -2,15 +2,13 @@ import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:intl/intl.dart';
 import 'package:ristoris/core/credit_score_color.dart';
-import '../../../database/daos/sconti_dao.dart';
 import '../../../providers/auth_provider.dart';
 import '../../widgets/cod_desc_dropdown_item.dart';
 import '../../../providers/order_provider.dart';
 import '../../../providers/database_provider.dart';
 import '../../../database/database.dart';
-import '../../../database/daos/listini_dao.dart';
-import '../../widgets/article_image.dart';
 import 'selezione_cliente_page.dart';
+import 'selezione_articolo_page.dart';
 
 class OrderEditPage extends ConsumerStatefulWidget {
   final int? id;
@@ -25,15 +23,10 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
     with SingleTickerProviderStateMixin {
   late TabController _tabController;
 
-  final _artCtrl = TextEditingController();
   final _noteClienteCtrl = TextEditingController();
   final _noteOrdineCtrl = TextEditingController();
   final _riferimentoCtrl = TextEditingController();
 
-  List<Articolo> _searchResults = [];
-  bool _searching = false;
-  bool _usaListinoCliente = true;
-
   List<Filiale> _filiali = [];
   List<Listino> _listini = [];
   List<Pagamento> _pagamenti = [];
@@ -49,18 +42,13 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
   void initState() {
     super.initState();
     _tabController = TabController(length: 2, vsync: this);
-    _tabController.addListener(() {
-      if (_tabController.index == 1 && _searchResults.isEmpty && _artCtrl.text.isEmpty) {
-        _searchArticles('');
-      }
-    });
+    _tabController.addListener(() => setState(() {}));
     WidgetsBinding.instance.addPostFrameCallback((_) => _init());
   }
 
   @override
   void dispose() {
     _tabController.dispose();
-    _artCtrl.dispose();
     _noteClienteCtrl.dispose();
     _noteOrdineCtrl.dispose();
     _riferimentoCtrl.dispose();
@@ -109,86 +97,39 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
     });
   }
 
-  Future<void> _searchArticles(String q) async {
-    setState(() => _searching = true);
-    final dao = ref.read(articoliDaoProvider);
-    final order = ref.read(orderProvider).order;
-    final dataOrdine = order?.dataOrdine ?? DateTime.now().toIso8601String();
-    List<Articolo> results;
-    if (_usaListinoCliente && _cliente != null) {
-      results = await dao.getArticoliPerListinoCliente(_cliente!.idCliente, dataOrdine);
-    } else {
-      results = await dao.getArticoliPerListinoAttivo(dataOrdine);
-    }
-    if (q.isNotEmpty) {
-      final lower = q.toLowerCase();
-      results = results.where((a) {
-        final cod = (a.codArticolo ?? '').toLowerCase();
-        final desc = (a.descrizione ?? '').toLowerCase();
-        final ean = (a.ean ?? '').toLowerCase();
-        return cod.contains(lower) || desc.contains(lower) || ean.contains(lower);
-      }).toList();
-    }
-    setState(() {
-      _searchResults = results;
-      _searching = false;
-    });
-  }
-
-  void _showArticlePicker(Articolo art) async {
-    final agente = ref.read(authProvider).agente;
-    final canEditPrice = agente?.modificaPrezzi == 1;
+  void _aggiungiArticolo() async {
     final order = ref.read(orderProvider).order;
     final dataOrdine = order?.dataOrdine ?? DateTime.now().toIso8601String();
-    final listini = await ref
-        .read(listiniDaoProvider)
-        .getListiniPerArticolo(art.idArticolo, dataOrdine);
-    final listiniDao = ref.read(listiniDaoProvider);
-    final scontiDao = ref.read(scontiDaoProvider);
-    if (!context.mounted) return;
 
-    showModalBottomSheet(
-      context: context,
-      isScrollControlled: true,
-      builder: (_) => _ArticlePickerSheet(
-        articolo: art,
-        listini: listini,
-        listiniDao: listiniDao,
-        scontiDao: scontiDao,
-        dataOrdine: dataOrdine,
-        canEditPrezzo: canEditPrice,
-        idListinoDefault: _selectedListino,
-        onConfirm: (
-          colli,
-          quantita,
-          prezzoLordo,
-          prezzoNetto,
-          listinoId, {
-          double sconto1 = 0,
-          double sconto2 = 0,
-          double sconto3 = 0,
-          double sconto4 = 0,
-          double sconto5 = 0,
-        }) {
-          _addArticleWithDetails(
-            art: art,
-            idListino: listinoId,
-            colli: colli,
-            quantita: quantita,
-            prezzoLordo: prezzoLordo,
-            prezzoNetto: prezzoNetto,
-            sconto1: sconto1,
-            sconto2: sconto2,
-            sconto3: sconto3,
-            sconto4: sconto4,
-            sconto5: sconto5,
-          );
-        },
+    final result = await Navigator.push<ArticleSelectionResult>(
+      context,
+      MaterialPageRoute(
+        builder: (_) => SelezioneArticoloPage(
+          idCliente: _cliente?.idCliente ?? 0,
+          dataOrdine: dataOrdine,
+          idListinoDefault: _selectedListino,
+        ),
       ),
     );
+
+    if (result != null) {
+      _addArticleWithDetails(
+        art: result.articolo,
+        idListino: result.idListino,
+        colli: result.colli,
+        quantita: result.quantita,
+        prezzoLordo: result.prezzoLordo,
+        prezzoNetto: result.prezzoNetto,
+        sconto1: result.sconto1,
+        sconto2: result.sconto2,
+        sconto3: result.sconto3,
+        sconto4: result.sconto4,
+        sconto5: result.sconto5,
+      );
+    }
   }
 
-  Future<void> _addArticleWithDetails({
+  void _addArticleWithDetails({
     required Articolo art,
     required int idListino,
     required double colli,
@@ -200,21 +141,11 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
     double? sconto3 = 0,
     double? sconto4 = 0,
     double? sconto5 = 0,
-  }) async {
-    final orderState = ref.read(orderProvider);
-    var idOrdine = orderState.order?.idOrdine;
-    if (idOrdine == null || idOrdine == 0) {
-      await ref.read(orderProvider.notifier).saveOrder();
-      idOrdine = ref.read(orderProvider).order?.idOrdine;
-      if (idOrdine == null || idOrdine == 0) return;
-    }
-
-    await ref
-        .read(orderProvider.notifier)
-        .addLine(
+  }) {
+    ref.read(orderProvider.notifier).addLine(
           RigaOrdine(
             idRigaOrdine: 0,
-            idOrdine: idOrdine,
+            idOrdine: ref.read(orderProvider).order?.idOrdine ?? 0,
             idArticolo: art.idArticolo,
             idListino: idListino,
             quantita: quantita,
@@ -231,8 +162,6 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
             colli: colli,
           ),
         );
-    _artCtrl.clear();
-    setState(() => _searchResults = []);
   }
 
   Future<void> _selezionaCliente() async {
@@ -284,6 +213,12 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
     return Scaffold(
       appBar: AppBar(
         title: Text('Ordine #${orderState.order?.numeroOrdine ?? 'nuovo'}'),
+        actions: [
+          IconButton(
+            icon: const Icon(Icons.save),
+            onPressed: _salva,
+          ),
+        ],
         bottom: TabBar(
           controller: _tabController,
           tabs: const [
@@ -303,12 +238,10 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
                 ],
               ),
       ),
-      floatingActionButton:
-          _tabController.index == 1 && orderState.order?.idOrdine != null
-          ? FloatingActionButton.extended(
-              onPressed: _salva,
-              icon: const Icon(Icons.save),
-              label: const Text('Salva'),
+      floatingActionButton: _tabController.index == 1 && _cliente != null
+          ? FloatingActionButton(
+              onPressed: _aggiungiArticolo,
+              child: const Icon(Icons.add),
             )
           : null,
     );
@@ -485,58 +418,6 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
   Widget _buildLinesTab(ThemeData theme, OrderState orderState) {
     return Column(
       children: [
-        Padding(
-          padding: const EdgeInsets.all(12),
-          child: Row(
-            children: [
-              Expanded(
-                child: TextField(
-                  controller: _artCtrl,
-                  decoration: InputDecoration(
-                    hintText: 'Cerca articolo...',
-                    prefixIcon: const Icon(Icons.search),
-                    suffixIcon: _searching
-                        ? const SizedBox(
-                            width: 20,
-                            height: 20,
-                            child: CircularProgressIndicator(strokeWidth: 2),
-                          )
-                        : null,
-                  ),
-                  onChanged: _searchArticles,
-                ),
-              ),
-              const SizedBox(width: 8),
-              FilterChip(
-                label: const Text('Listino cliente'),
-                selected: _usaListinoCliente,
-                onSelected: (v) {
-                  setState(() => _usaListinoCliente = v);
-                  _searchArticles(_artCtrl.text);
-                },
-              ),
-            ],
-          ),
-        ),
-        if (_searchResults.isNotEmpty)
-          SizedBox(
-            height: 200,
-            child: ListView.builder(
-              itemCount: _searchResults.length,
-              itemBuilder: (_, i) => ListTile(
-                leading: ArticleImage(
-                  idArticolo: _searchResults[i].idArticolo,
-                  codArticolo: _searchResults[i].codArticolo,
-                ),
-                title: Text(_searchResults[i].descrizione ?? ''),
-                subtitle: Text(_searchResults[i].codArticolo ?? ''),
-                trailing: TextButton(
-                  onPressed: () => _showArticlePicker(_searchResults[i]),
-                  child: const Text('Aggiungi'),
-                ),
-              ),
-            ),
-          ),
         Expanded(
           child: orderState.lines.isEmpty
               ? const Center(child: Text('Nessun articolo'))
@@ -544,9 +425,8 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
                   itemCount: orderState.lines.length,
                   itemBuilder: (_, i) => _LineCard(
                     line: orderState.lines[i],
-                    onDelete: () => ref
-                        .read(orderProvider.notifier)
-                        .removeLine(orderState.lines[i].idRigaOrdine),
+                    onDelete: () =>
+                        ref.read(orderProvider.notifier).removeLine(i),
                     theme: theme,
                   ),
                 ),
@@ -626,383 +506,3 @@ class _LineCard extends StatelessWidget {
     );
   }
 }
-
-class _ArticlePickerSheet extends StatefulWidget {
-  final Articolo articolo;
-  final List<Listino> listini;
-  final ListiniDao listiniDao;
-  final ScontiDao scontiDao;
-  final String dataOrdine;
-  final bool canEditPrezzo;
-  final int? idListinoDefault;
-  final void Function(
-    double colli,
-    double quantita,
-    double prezzoLordo,
-    double prezzoNetto,
-    int listinoId, {
-    double sconto1,
-    double sconto2,
-    double sconto3,
-    double sconto4,
-    double sconto5,
-  })
-  onConfirm;
-
-  const _ArticlePickerSheet({
-    required this.articolo,
-    required this.listini,
-    required this.listiniDao,
-    required this.scontiDao,
-    required this.dataOrdine,
-    required this.canEditPrezzo,
-    this.idListinoDefault,
-    required this.onConfirm,
-  });
-
-  @override
-  State<_ArticlePickerSheet> createState() => _ArticlePickerSheetState();
-}
-
-class _ArticlePickerSheetState extends State<_ArticlePickerSheet> {
-  final _colliCtrl = TextEditingController();
-  final _qtaCtrl = TextEditingController();
-  final _prezzoCtrl = TextEditingController();
-  final _sconto1Ctrl = TextEditingController();
-  final _sconto2Ctrl = TextEditingController();
-  final _sconto3Ctrl = TextEditingController();
-  final _sconto4Ctrl = TextEditingController();
-  final _sconto5Ctrl = TextEditingController();
-
-  int? _selectedListino;
-  double _prezzo = 0;
-  double _prezzoNetto = 0;
-  double _sconto1 = 0;
-  double _sconto2 = 0;
-  double _sconto3 = 0;
-  double _sconto4 = 0;
-  double _sconto5 = 0;
-
-  bool _updatingColli = false;
-  bool _updatingQta = false;
-
-  @override
-  void initState() {
-    super.initState();
-    _selectedListino = widget.idListinoDefault;
-    if (_selectedListino != null) {
-      _loadPrezzo(_selectedListino!);
-    }
-  }
-
-  @override
-  void dispose() {
-    _colliCtrl.dispose();
-    _qtaCtrl.dispose();
-    _prezzoCtrl.dispose();
-    _sconto1Ctrl.dispose();
-    _sconto2Ctrl.dispose();
-    _sconto3Ctrl.dispose();
-    _sconto4Ctrl.dispose();
-    _sconto5Ctrl.dispose();
-    super.dispose();
-  }
-
-  Future<void> _loadPrezzo(int idListino) async {
-    final lp = await widget.listiniDao.findListinoArticolo(
-      idListino,
-      widget.articolo.idArticolo,
-    );
-    if (lp != null) {
-      if (lp.prezzo != null) {
-        setState(() => _prezzo = lp.prezzo!);
-        _prezzoCtrl.text = _prezzo.toStringAsFixed(2);
-      }
-      if (lp.idSconto != null) {
-        final sconto = await widget.scontiDao.findById(lp.idSconto!);
-        setState(() {
-          _sconto1 = sconto?.sconto1 ?? 0;
-          _sconto2 = sconto?.sconto2 ?? 0;
-          _sconto3 = sconto?.sconto3 ?? 0;
-          _sconto4 = sconto?.sconto4 ?? 0;
-          _sconto5 = sconto?.sconto5 ?? 0;
-        });
-        _sconto1Ctrl.text = _sconto1 > 0 ? _sconto1.toStringAsFixed(1) : '';
-        _sconto2Ctrl.text = _sconto2 > 0 ? _sconto2.toStringAsFixed(1) : '';
-        _sconto3Ctrl.text = _sconto3 > 0 ? _sconto3.toStringAsFixed(1) : '';
-        _sconto4Ctrl.text = _sconto4 > 0 ? _sconto4.toStringAsFixed(1) : '';
-        _sconto5Ctrl.text = _sconto5 > 0 ? _sconto5.toStringAsFixed(1) : '';
-      } else {
-        _resetSconti();
-      }
-      _calcolaPrezzoNetto();
-    }
-  }
-
-  void _resetSconti() {
-    setState(() {
-      _sconto1 = 0;
-      _sconto2 = 0;
-      _sconto3 = 0;
-      _sconto4 = 0;
-      _sconto5 = 0;
-    });
-    _sconto1Ctrl.clear();
-    _sconto2Ctrl.clear();
-    _sconto3Ctrl.clear();
-    _sconto4Ctrl.clear();
-    _sconto5Ctrl.clear();
-  }
-
-  void _calcolaPrezzoNetto() {
-    double n = _prezzo;
-    n *= (100 - _sconto1) / 100;
-    n *= (100 - _sconto2) / 100;
-    n *= (100 - _sconto3) / 100;
-    n *= (100 - _sconto4) / 100;
-    n *= (100 - _sconto5) / 100;
-    setState(() => _prezzoNetto = n);
-  }
-
-  void _onPrezzoChanged(String v) {
-    setState(() => _prezzo = double.tryParse(v) ?? 0);
-    _calcolaPrezzoNetto();
-  }
-
-  void _onScontoChanged(int index, String v) {
-    final val = double.tryParse(v) ?? 0;
-    setState(() {
-      switch (index) {
-        case 1:
-          _sconto1 = val;
-        case 2:
-          _sconto2 = val;
-        case 3:
-          _sconto3 = val;
-        case 4:
-          _sconto4 = val;
-        case 5:
-          _sconto5 = val;
-      }
-    });
-    _calcolaPrezzoNetto();
-  }
-
-  void _onColliChanged(String v) {
-    if (_updatingColli) return;
-    _updatingQta = true;
-    final colli = double.tryParse(v) ?? 0;
-    final pezzi = widget.articolo.pezziConfezione ?? 0;
-    if (colli > 0 && pezzi > 0) {
-      _qtaCtrl.text = (colli * pezzi).toStringAsFixed(1);
-    } else if (colli == 0) {
-      _qtaCtrl.text = '';
-    }
-    _updatingQta = false;
-  }
-
-  void _onQtaChanged(String v) {
-    if (_updatingQta) return;
-    _updatingColli = true;
-    final qta = double.tryParse(v) ?? 0;
-    if (qta > 0) {
-      _colliCtrl.text = '0';
-    } else if (qta == 0) {
-      _colliCtrl.text = '';
-    }
-    _updatingColli = false;
-  }
-
-  void _confirm() {
-    final colli = double.tryParse(_colliCtrl.text) ?? 0;
-    final qta = double.tryParse(_qtaCtrl.text) ?? 0;
-    final prezzoLordo = double.tryParse(_prezzoCtrl.text) ?? 0;
-    final listinoId = _selectedListino ?? 0;
-    widget.onConfirm(
-      colli,
-      qta,
-      prezzoLordo,
-      _prezzoNetto,
-      listinoId,
-      sconto1: _sconto1,
-      sconto2: _sconto2,
-      sconto3: _sconto3,
-      sconto4: _sconto4,
-      sconto5: _sconto5,
-    );
-    Navigator.pop(context);
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = Theme.of(context);
-    return Padding(
-      padding: EdgeInsets.only(
-        bottom: MediaQuery.of(context).viewInsets.bottom,
-        left: 16,
-        right: 16,
-        top: 16,
-      ),
-      child: SingleChildScrollView(
-        child: Column(
-          mainAxisSize: MainAxisSize.min,
-          crossAxisAlignment: CrossAxisAlignment.start,
-          children: [
-            Text(
-              widget.articolo.descrizione ?? '',
-              style: theme.textTheme.titleMedium,
-            ),
-            const SizedBox(height: 4),
-            Text(
-              widget.articolo.codArticolo ?? '',
-              style: theme.textTheme.bodySmall,
-            ),
-            if (widget.articolo.pezziConfezione != null)
-              Text(
-                'Pezzi/confezione: ${widget.articolo.pezziConfezione}',
-                style: theme.textTheme.bodySmall,
-              ),
-            const SizedBox(height: 16),
-            DropdownButtonFormField<int>(
-              value: _selectedListino,
-              decoration: const InputDecoration(labelText: 'Listino'),
-              isExpanded: true,
-              items: widget.listini
-                  .map(
-                    (l) => DropdownMenuItem(
-                      value: l.idListino,
-                      child: CodDescDropdownItem(
-                        codice: l.codListino,
-                        descrizione: l.descrizione,
-                      ),
-                    ),
-                  )
-                  .toList(),
-              onChanged: (v) {
-                setState(() => _selectedListino = v);
-                if (v != null) _loadPrezzo(v);
-              },
-            ),
-            const SizedBox(height: 12),
-            TextField(
-              controller: _prezzoCtrl,
-              decoration: const InputDecoration(labelText: 'Prezzo lordo'),
-              keyboardType: const TextInputType.numberWithOptions(
-                decimal: true,
-              ),
-              enabled: widget.canEditPrezzo,
-              onChanged: _onPrezzoChanged,
-            ),
-            const SizedBox(height: 12),
-            Row(
-              children: [
-                Expanded(
-                  child: TextField(
-                    controller: _sconto1Ctrl,
-                    decoration: const InputDecoration(labelText: 'Sconto 1 %'),
-                    keyboardType: const TextInputType.numberWithOptions(
-                      decimal: true,
-                    ),
-                    onChanged: (v) => _onScontoChanged(1, v),
-                  ),
-                ),
-                const SizedBox(width: 12),
-                Expanded(
-                  child: TextField(
-                    controller: _sconto2Ctrl,
-                    decoration: const InputDecoration(labelText: 'Sconto 2 %'),
-                    keyboardType: const TextInputType.numberWithOptions(
-                      decimal: true,
-                    ),
-                    onChanged: (v) => _onScontoChanged(2, v),
-                  ),
-                ),
-              ],
-            ),
-            const SizedBox(height: 12),
-            Row(
-              children: [
-                Expanded(
-                  child: TextField(
-                    controller: _sconto3Ctrl,
-                    decoration: const InputDecoration(labelText: 'Sconto 3 %'),
-                    keyboardType: const TextInputType.numberWithOptions(
-                      decimal: true,
-                    ),
-                    onChanged: (v) => _onScontoChanged(3, v),
-                  ),
-                ),
-                const SizedBox(width: 12),
-                Expanded(
-                  child: TextField(
-                    controller: _sconto4Ctrl,
-                    decoration: const InputDecoration(labelText: 'Sconto 4 %'),
-                    keyboardType: const TextInputType.numberWithOptions(
-                      decimal: true,
-                    ),
-                    onChanged: (v) => _onScontoChanged(4, v),
-                  ),
-                ),
-              ],
-            ),
-            const SizedBox(height: 12),
-            SizedBox(
-              width: 140,
-              child: TextField(
-                controller: _sconto5Ctrl,
-                decoration: const InputDecoration(labelText: 'Sconto 5 %'),
-                keyboardType: const TextInputType.numberWithOptions(
-                  decimal: true,
-                ),
-                onChanged: (v) => _onScontoChanged(5, v),
-              ),
-            ),
-            const SizedBox(height: 16),
-            Row(
-              children: [
-                Text('Prezzo netto: ', style: theme.textTheme.bodyLarge),
-                Text(
-                  _prezzoNetto.toStringAsFixed(2),
-                  style: theme.textTheme.bodyLarge?.copyWith(
-                    fontWeight: FontWeight.bold,
-                  ),
-                ),
-              ],
-            ),
-            const SizedBox(height: 12),
-            Row(
-              children: [
-                Expanded(
-                  child: TextField(
-                    controller: _colliCtrl,
-                    decoration: const InputDecoration(labelText: 'Colli'),
-                    keyboardType: TextInputType.number,
-                    onChanged: _onColliChanged,
-                  ),
-                ),
-                const SizedBox(width: 12),
-                Expanded(
-                  child: TextField(
-                    controller: _qtaCtrl,
-                    decoration: const InputDecoration(labelText: 'Quantità'),
-                    keyboardType: TextInputType.number,
-                    onChanged: _onQtaChanged,
-                  ),
-                ),
-              ],
-            ),
-            const SizedBox(height: 20),
-            SizedBox(
-              width: double.infinity,
-              child: ElevatedButton(
-                onPressed: _confirm,
-                child: const Text('Aggiungi'),
-              ),
-            ),
-            const SizedBox(height: 8),
-          ],
-        ),
-      ),
-    );
-  }
-}

+ 35 - 30
lib/ui/pages/orders/order_list_page.dart

@@ -97,9 +97,8 @@ class _OrderListPageState extends ConsumerState<OrderListPage> {
           )
         : null;
 
-    final statsAsync = agente != null
-        ? ref.watch(orderStatsProvider(agente.idAgente))
-        : null;
+    final statsAsync =
+        agente != null ? ref.watch(orderStatsProvider(agente.idAgente)) : null;
 
     return Scaffold(
       appBar: AppBar(title: const Text('Lista Ordini')),
@@ -120,8 +119,7 @@ class _OrderListPageState extends ConsumerState<OrderListPage> {
               },
             ),
             Expanded(
-              child:
-                  ordersAsync?.when(
+              child: ordersAsync?.when(
                     data: (orders) {
                       if (orders.isEmpty) {
                         return const EmptyState(
@@ -342,13 +340,11 @@ class _OrderTable extends StatelessWidget {
                                               Text(
                                                 '${o.numeroOrdine ?? ''}',
                                                 style: theme
-                                                    .textTheme
-                                                    .bodyMedium
+                                                    .textTheme.bodyMedium
                                                     ?.copyWith(
-                                                      fontWeight:
-                                                          FontWeight.bold,
-                                                      fontFamily: 'monospace',
-                                                    ),
+                                                  fontWeight: FontWeight.bold,
+                                                  fontFamily: 'monospace',
+                                                ),
                                               ),
                                               const SizedBox(height: 4),
                                               Text(
@@ -361,9 +357,8 @@ class _OrderTable extends StatelessWidget {
                                                     : '',
                                                 style: theme.textTheme.bodySmall
                                                     ?.copyWith(
-                                                      color:
-                                                          cs.onSurfaceVariant,
-                                                    ),
+                                                  color: cs.onSurfaceVariant,
+                                                ),
                                               ),
                                             ],
                                           ),
@@ -388,9 +383,8 @@ class _OrderTable extends StatelessWidget {
                                                 '${o.filiale ?? ''}${o.indirizzo ?? ''} ${o.citta ?? ''}${o.provincia != null ? ' (${o.provincia})' : ''}',
                                                 style: theme.textTheme.bodySmall
                                                     ?.copyWith(
-                                                      color:
-                                                          cs.onSurfaceVariant,
-                                                    ),
+                                                  color: cs.onSurfaceVariant,
+                                                ),
                                                 maxLines: 1,
                                                 overflow: TextOverflow.ellipsis,
                                               ),
@@ -409,8 +403,8 @@ class _OrderTable extends StatelessWidget {
                                               ),
                                               style: theme.textTheme.bodyMedium
                                                   ?.copyWith(
-                                                    fontWeight: FontWeight.bold,
-                                                  ),
+                                                fontWeight: FontWeight.bold,
+                                              ),
                                               textAlign: TextAlign.right,
                                             ),
                                           ),
@@ -427,9 +421,20 @@ class _OrderTable extends StatelessWidget {
                                               size: 20,
                                               color: cs.secondary,
                                             ),
-                                            onPressed: () => context.push(
+                                            onPressed: () {
+                                              print(o.idOrdine);
+                                              context.push(
+                                                '/dashboard/orders/${o.idOrdine}',
+                                              );
+                                            },
+                                            /*
+                                            onPressed: () =>  {
+                                              context.push(
                                               '/dashboard/orders/${o.idOrdine}',
-                                            ),
+                                            )
+                                            }
+                                            ,
+                                            */
                                             visualDensity:
                                                 VisualDensity.compact,
                                           ),
@@ -442,9 +447,9 @@ class _OrderTable extends StatelessWidget {
                                             '${o.numeroOrdine ?? ''}',
                                             style: theme.textTheme.bodyMedium
                                                 ?.copyWith(
-                                                  fontWeight: FontWeight.bold,
-                                                  fontFamily: 'monospace',
-                                                ),
+                                              fontWeight: FontWeight.bold,
+                                              fontFamily: 'monospace',
+                                            ),
                                           ),
                                           flex: 1,
                                         ),
@@ -459,8 +464,8 @@ class _OrderTable extends StatelessWidget {
                                                 : '',
                                             style: theme.textTheme.bodyMedium
                                                 ?.copyWith(
-                                                  color: cs.onSurfaceVariant,
-                                                ),
+                                              color: cs.onSurfaceVariant,
+                                            ),
                                           ),
                                           flex: 2,
                                         ),
@@ -478,8 +483,8 @@ class _OrderTable extends StatelessWidget {
                                             '${o.filiale ?? ''}${o.indirizzo ?? ''} ${o.citta ?? ''}${o.provincia != null ? ' (${o.provincia})' : ''}',
                                             style: theme.textTheme.bodyMedium
                                                 ?.copyWith(
-                                                  color: cs.onSurfaceVariant,
-                                                ),
+                                              color: cs.onSurfaceVariant,
+                                            ),
                                             maxLines: 2,
                                             overflow: TextOverflow.ellipsis,
                                           ),
@@ -496,8 +501,8 @@ class _OrderTable extends StatelessWidget {
                                               ),
                                               style: theme.textTheme.bodyMedium
                                                   ?.copyWith(
-                                                    fontWeight: FontWeight.bold,
-                                                  ),
+                                                fontWeight: FontWeight.bold,
+                                              ),
                                               textAlign: TextAlign.right,
                                             ),
                                           ),

+ 647 - 0
lib/ui/pages/orders/selezione_articolo_page.dart

@@ -0,0 +1,647 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:ristoris/database/daos/articoli_dao.dart';
+import '../../../database/daos/sconti_dao.dart';
+import '../../../providers/auth_provider.dart';
+import '../../widgets/cod_desc_dropdown_item.dart';
+import '../../../providers/database_provider.dart';
+import '../../../database/database.dart';
+import '../../widgets/article_image.dart';
+
+class ArticleSelectionResult {
+  final Articolo articolo;
+  final int idListino;
+  final double colli;
+  final double quantita;
+  final double prezzoLordo;
+  final double prezzoNetto;
+  final double sconto1;
+  final double sconto2;
+  final double sconto3;
+  final double sconto4;
+  final double sconto5;
+
+  const ArticleSelectionResult({
+    required this.articolo,
+    required this.idListino,
+    required this.colli,
+    required this.quantita,
+    required this.prezzoLordo,
+    required this.prezzoNetto,
+    this.sconto1 = 0,
+    this.sconto2 = 0,
+    this.sconto3 = 0,
+    this.sconto4 = 0,
+    this.sconto5 = 0,
+  });
+}
+
+class SelezioneArticoloPage extends ConsumerStatefulWidget {
+  final int idCliente;
+  final String dataOrdine;
+  final int? idListinoDefault;
+
+  const SelezioneArticoloPage({
+    super.key,
+    required this.idCliente,
+    required this.dataOrdine,
+    this.idListinoDefault,
+  });
+
+  @override
+  ConsumerState<SelezioneArticoloPage> createState() =>
+      _SelezioneArticoloPageState();
+}
+
+class _SelezioneArticoloPageState extends ConsumerState<SelezioneArticoloPage> {
+  final _artCtrl = TextEditingController();
+  List<Articolo> _searchResults = [];
+  bool _searching = false;
+  bool _usaListinoCliente = true;
+  Map<int, Listino> _mapListini = {};
+
+  @override
+  void initState() {
+    super.initState();
+    _loadListini();
+    _searchArticles('');
+  }
+
+  @override
+  void dispose() {
+    _artCtrl.dispose();
+    super.dispose();
+  }
+
+  Future<void> _loadListini() async {
+    final listini = await ref.read(listiniDaoProvider).listSenzaFiltro();
+    setState(() {
+      _mapListini = {for (final l in listini) l.idListino: l};
+    });
+  }
+
+  Future<void> _searchArticles(String q) async {
+    setState(() => _searching = true);
+    final dao = ref.read(articoliDaoProvider);
+    List<Articolo> results;
+    if (_usaListinoCliente && widget.idCliente != 0) {
+      results = await dao.getArticoliPerListinoCliente(
+        widget.idCliente,
+        widget.dataOrdine,
+      );
+    } else {
+      results = await dao.getArticoliPerListinoAttivo(widget.dataOrdine, q);
+    }
+    if (q.isNotEmpty) {
+      final lower = q.toLowerCase();
+      results = results.where((a) {
+        final cod = (a.codArticolo ?? '').toLowerCase();
+        final desc = (a.descrizione ?? '').toLowerCase();
+        final ean = (a.ean ?? '').toLowerCase();
+        return cod.contains(lower) ||
+            desc.contains(lower) ||
+            ean.contains(lower);
+      }).toList();
+    }
+    setState(() {
+      _searchResults = results;
+      _searching = false;
+    });
+  }
+
+  void _showArticlePicker(Articolo art) async {
+    final agente = ref.read(authProvider).agente;
+    final canEditPrice = agente?.modificaPrezzi == 1;
+
+    final listini = await ref
+        .read(articoliDaoProvider)
+        .getListiniArticoloValidi(art.idArticolo, widget.dataOrdine);
+
+/*
+    final seen = <int>{};
+    final listini = listiniArticolo
+        .map((la) => _mapListini[la.idListino])
+        .whereType<Listino>()
+        .where((l) => seen.add(l.idListino))
+        .toList();
+*/
+
+    final scontiDao = ref.read(scontiDaoProvider);
+    final articoliDao = ref.read(articoliDaoProvider);
+
+    if (!context.mounted) return;
+
+    final sheetResult = await showModalBottomSheet<ArticleSelectionResult>(
+      context: context,
+      isScrollControlled: true,
+      builder: (_) => _ArticlePickerSheet(
+        articolo: art,
+        listini: listini,
+        articoliDao: articoliDao,
+        scontiDao: scontiDao,
+        dataOrdine: widget.dataOrdine,
+        canEditPrezzo: canEditPrice,
+        idListinoDefault: widget.idListinoDefault,
+        onConfirm: (
+          colli,
+          quantita,
+          prezzoLordo,
+          prezzoNetto,
+          listinoId, {
+          double sconto1 = 0,
+          double sconto2 = 0,
+          double sconto3 = 0,
+          double sconto4 = 0,
+          double sconto5 = 0,
+        }) {
+          Navigator.pop(
+            context,
+            ArticleSelectionResult(
+              articolo: art,
+              idListino: listinoId,
+              colli: colli,
+              quantita: quantita,
+              prezzoLordo: prezzoLordo,
+              prezzoNetto: prezzoNetto,
+              sconto1: sconto1,
+              sconto2: sconto2,
+              sconto3: sconto3,
+              sconto4: sconto4,
+              sconto5: sconto5,
+            ),
+          );
+        },
+      ),
+    );
+
+    if (sheetResult != null && context.mounted) {
+      Navigator.pop(context, sheetResult);
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text('Selezione articolo'),
+      ),
+      body: SafeArea(
+        child: Column(
+          children: [
+            Padding(
+              padding: const EdgeInsets.all(12),
+              child: Row(
+                children: [
+                  Expanded(
+                    child: TextField(
+                      controller: _artCtrl,
+                      decoration: InputDecoration(
+                        hintText: 'Cerca articolo...',
+                        prefixIcon: const Icon(Icons.search),
+                        suffixIcon: _searching
+                            ? const SizedBox(
+                                width: 20,
+                                height: 20,
+                                child: CircularProgressIndicator(
+                                  strokeWidth: 2,
+                                ),
+                              )
+                            : null,
+                      ),
+                      onChanged: _searchArticles,
+                    ),
+                  ),
+                  const SizedBox(width: 8),
+                  FilterChip(
+                    label: const Text('Listino cliente'),
+                    selected: _usaListinoCliente,
+                    onSelected: (v) {
+                      setState(() => _usaListinoCliente = v);
+                      _searchArticles(_artCtrl.text);
+                    },
+                  ),
+                ],
+              ),
+            ),
+            Expanded(
+              child: _searchResults.isEmpty
+                  ? Center(
+                      child: Text(
+                        _searching ? 'Ricerca...' : 'Nessun articolo trovato',
+                      ),
+                    )
+                  : ListView.builder(
+                      itemCount: _searchResults.length,
+                      itemBuilder: (_, i) => ListTile(
+                        leading: ArticleImage(
+                          idArticolo: _searchResults[i].idArticolo,
+                          codArticolo: _searchResults[i].codArticolo,
+                        ),
+                        title: Text(_searchResults[i].descrizione ?? ''),
+                        subtitle: Text(_searchResults[i].codArticolo ?? ''),
+                        trailing: TextButton(
+                          onPressed: () =>
+                              _showArticlePicker(_searchResults[i]),
+                          child: const Text('Aggiungi'),
+                        ),
+                      ),
+                    ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
+
+class _ArticlePickerSheet extends StatefulWidget {
+  final Articolo articolo;
+//  final List<ListinoArticolo> listiniArticolo;
+  final List<Listino> listini;
+  final ArticoliDao articoliDao;
+  final ScontiDao scontiDao;
+
+  final String dataOrdine;
+  final bool canEditPrezzo;
+  final int? idListinoDefault;
+  final void Function(
+    double colli,
+    double quantita,
+    double prezzoLordo,
+    double prezzoNetto,
+    int listinoId, {
+    double sconto1,
+    double sconto2,
+    double sconto3,
+    double sconto4,
+    double sconto5,
+  }) onConfirm;
+
+  const _ArticlePickerSheet({
+    required this.articolo,
+    required this.listini,
+    required this.articoliDao,
+    required this.scontiDao,
+    required this.dataOrdine,
+    required this.canEditPrezzo,
+    this.idListinoDefault,
+    required this.onConfirm,
+  });
+
+  @override
+  State<_ArticlePickerSheet> createState() => _ArticlePickerSheetState();
+}
+
+class _ArticlePickerSheetState extends State<_ArticlePickerSheet> {
+  final _colliCtrl = TextEditingController();
+  final _qtaCtrl = TextEditingController();
+  final _prezzoCtrl = TextEditingController();
+  final _sconto1Ctrl = TextEditingController();
+  final _sconto2Ctrl = TextEditingController();
+  final _sconto3Ctrl = TextEditingController();
+  final _sconto4Ctrl = TextEditingController();
+  final _sconto5Ctrl = TextEditingController();
+
+  int? _selectedListino;
+  double _prezzo = 0;
+  double _prezzoNetto = 0;
+  double _sconto1 = 0;
+  double _sconto2 = 0;
+  double _sconto3 = 0;
+  double _sconto4 = 0;
+  double _sconto5 = 0;
+
+  bool _updatingColli = false;
+  bool _updatingQta = false;
+
+  @override
+  void initState() {
+    super.initState();
+    _selectedListino = widget.idListinoDefault;
+    if (_selectedListino != null &&
+        !widget.listini.any((l) => l.idListino == _selectedListino)) {
+      _selectedListino = null;
+    }
+    if (_selectedListino != null) {
+      _loadPrezzo(_selectedListino!, widget.articolo.idArticolo);
+    }
+  }
+
+  @override
+  void dispose() {
+    _colliCtrl.dispose();
+    _qtaCtrl.dispose();
+    _prezzoCtrl.dispose();
+    _sconto1Ctrl.dispose();
+    _sconto2Ctrl.dispose();
+    _sconto3Ctrl.dispose();
+    _sconto4Ctrl.dispose();
+    _sconto5Ctrl.dispose();
+    super.dispose();
+  }
+
+  void _loadPrezzo(int idListino, int idaArticolo) async {
+    final lp = await widget.articoliDao
+        .getListPrice(idListino, idaArticolo, widget.dataOrdine);
+
+/*
+    final lp = widget.listiniArticolo.cast<ListinoArticolo?>().firstWhere(
+          (la) => la!.idListino == idListino,
+          orElse: () => null,
+        );
+*/
+    if (lp != null) {
+      if (lp.prezzo != null) {
+        setState(() => _prezzo = lp.prezzo!);
+        _prezzoCtrl.text = _prezzo.toStringAsFixed(2);
+      }
+      if (lp.idSconto != null) {
+        _loadSconti(lp.idSconto!);
+      } else {
+        _resetSconti();
+      }
+      _calcolaPrezzoNetto();
+    }
+  }
+
+  Future<void> _loadSconti(int idSconto) async {
+    final sconto = await widget.scontiDao.findById(idSconto);
+    setState(() {
+      _sconto1 = sconto?.sconto1 ?? 0;
+      _sconto2 = sconto?.sconto2 ?? 0;
+      _sconto3 = sconto?.sconto3 ?? 0;
+      _sconto4 = sconto?.sconto4 ?? 0;
+      _sconto5 = sconto?.sconto5 ?? 0;
+    });
+    _sconto1Ctrl.text = _sconto1 > 0 ? _sconto1.toStringAsFixed(1) : '';
+    _sconto2Ctrl.text = _sconto2 > 0 ? _sconto2.toStringAsFixed(1) : '';
+    _sconto3Ctrl.text = _sconto3 > 0 ? _sconto3.toStringAsFixed(1) : '';
+    _sconto4Ctrl.text = _sconto4 > 0 ? _sconto4.toStringAsFixed(1) : '';
+    _sconto5Ctrl.text = _sconto5 > 0 ? _sconto5.toStringAsFixed(1) : '';
+  }
+
+  void _resetSconti() {
+    setState(() {
+      _sconto1 = 0;
+      _sconto2 = 0;
+      _sconto3 = 0;
+      _sconto4 = 0;
+      _sconto5 = 0;
+    });
+    _sconto1Ctrl.clear();
+    _sconto2Ctrl.clear();
+    _sconto3Ctrl.clear();
+    _sconto4Ctrl.clear();
+    _sconto5Ctrl.clear();
+  }
+
+  void _calcolaPrezzoNetto() {
+    double n = _prezzo;
+    n *= (100 - _sconto1) / 100;
+    n *= (100 - _sconto2) / 100;
+    n *= (100 - _sconto3) / 100;
+    n *= (100 - _sconto4) / 100;
+    n *= (100 - _sconto5) / 100;
+    setState(() => _prezzoNetto = n);
+  }
+
+  void _onPrezzoChanged(String v) {
+    setState(() => _prezzo = double.tryParse(v) ?? 0);
+    _calcolaPrezzoNetto();
+  }
+
+  void _onScontoChanged(int index, String v) {
+    final val = double.tryParse(v) ?? 0;
+    setState(() {
+      switch (index) {
+        case 1:
+          _sconto1 = val;
+        case 2:
+          _sconto2 = val;
+        case 3:
+          _sconto3 = val;
+        case 4:
+          _sconto4 = val;
+        case 5:
+          _sconto5 = val;
+      }
+    });
+    _calcolaPrezzoNetto();
+  }
+
+  void _onColliChanged(String v) {
+    if (_updatingColli) return;
+    _updatingQta = true;
+    final colli = double.tryParse(v) ?? 0;
+    final pezzi = widget.articolo.pezziConfezione ?? 0;
+    if (colli > 0 && pezzi > 0) {
+      _qtaCtrl.text = (colli * pezzi).toStringAsFixed(1);
+    } else if (colli == 0) {
+      _qtaCtrl.text = '';
+    }
+    _updatingQta = false;
+  }
+
+  void _onQtaChanged(String v) {
+    if (_updatingQta) return;
+    _updatingColli = true;
+    final qta = double.tryParse(v) ?? 0;
+    if (qta > 0) {
+      _colliCtrl.text = '0';
+    } else if (qta == 0) {
+      _colliCtrl.text = '';
+    }
+    _updatingColli = false;
+  }
+
+  void _confirm() {
+    final colli = double.tryParse(_colliCtrl.text) ?? 0;
+    final qta = double.tryParse(_qtaCtrl.text) ?? 0;
+    final prezzoLordo = double.tryParse(_prezzoCtrl.text) ?? 0;
+    final listinoId = _selectedListino ?? 0;
+    widget.onConfirm(
+      colli,
+      qta,
+      prezzoLordo,
+      _prezzoNetto,
+      listinoId,
+      sconto1: _sconto1,
+      sconto2: _sconto2,
+      sconto3: _sconto3,
+      sconto4: _sconto4,
+      sconto5: _sconto5,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = Theme.of(context);
+    return Padding(
+      padding: EdgeInsets.only(
+        bottom: MediaQuery.of(context).viewInsets.bottom,
+        left: 16,
+        right: 16,
+        top: 16,
+      ),
+      child: SingleChildScrollView(
+        child: Column(
+          mainAxisSize: MainAxisSize.min,
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              widget.articolo.descrizione ?? '',
+              style: theme.textTheme.titleMedium,
+            ),
+            const SizedBox(height: 4),
+            Text(
+              widget.articolo.codArticolo ?? '',
+              style: theme.textTheme.bodySmall,
+            ),
+            if (widget.articolo.pezziConfezione != null)
+              Text(
+                'Pezzi/confezione: ${widget.articolo.pezziConfezione}',
+                style: theme.textTheme.bodySmall,
+              ),
+            const SizedBox(height: 16),
+            DropdownButtonFormField<int>(
+              // initialValue: _selectedListino,
+              decoration: const InputDecoration(labelText: 'Listino'),
+              isExpanded: true,
+              items: widget.listini.map((l) {
+                print(l);
+                return DropdownMenuItem(
+                  value: l.idListino,
+                  child: CodDescDropdownItem(
+                    codice: l.codListino,
+                    descrizione: l.descrizione,
+                  ),
+                );
+              }).toList(),
+              onChanged: (v) {
+                setState(() => _selectedListino = v);
+                if (v != null) _loadPrezzo(v, widget.articolo.idArticolo);
+              },
+            ),
+            const SizedBox(height: 12),
+            TextField(
+              controller: _prezzoCtrl,
+              decoration: const InputDecoration(labelText: 'Prezzo lordo'),
+              keyboardType: const TextInputType.numberWithOptions(
+                decimal: true,
+              ),
+              enabled: widget.canEditPrezzo,
+              onChanged: _onPrezzoChanged,
+            ),
+            const SizedBox(height: 12),
+            Row(
+              children: [
+                Expanded(
+                  child: TextField(
+                    controller: _sconto1Ctrl,
+                    decoration: const InputDecoration(labelText: 'Sconto 1 %'),
+                    keyboardType: const TextInputType.numberWithOptions(
+                      decimal: true,
+                    ),
+                    onChanged: (v) => _onScontoChanged(1, v),
+                  ),
+                ),
+                const SizedBox(width: 12),
+                Expanded(
+                  child: TextField(
+                    controller: _sconto2Ctrl,
+                    decoration: const InputDecoration(labelText: 'Sconto 2 %'),
+                    keyboardType: const TextInputType.numberWithOptions(
+                      decimal: true,
+                    ),
+                    onChanged: (v) => _onScontoChanged(2, v),
+                  ),
+                ),
+              ],
+            ),
+            const SizedBox(height: 12),
+            Row(
+              children: [
+                Expanded(
+                  child: TextField(
+                    controller: _sconto3Ctrl,
+                    decoration: const InputDecoration(labelText: 'Sconto 3 %'),
+                    keyboardType: const TextInputType.numberWithOptions(
+                      decimal: true,
+                    ),
+                    onChanged: (v) => _onScontoChanged(3, v),
+                  ),
+                ),
+                const SizedBox(width: 12),
+                Expanded(
+                  child: TextField(
+                    controller: _sconto4Ctrl,
+                    decoration: const InputDecoration(labelText: 'Sconto 4 %'),
+                    keyboardType: const TextInputType.numberWithOptions(
+                      decimal: true,
+                    ),
+                    onChanged: (v) => _onScontoChanged(4, v),
+                  ),
+                ),
+              ],
+            ),
+            const SizedBox(height: 12),
+            SizedBox(
+              width: 140,
+              child: TextField(
+                controller: _sconto5Ctrl,
+                decoration: const InputDecoration(labelText: 'Sconto 5 %'),
+                keyboardType: const TextInputType.numberWithOptions(
+                  decimal: true,
+                ),
+                onChanged: (v) => _onScontoChanged(5, v),
+              ),
+            ),
+            const SizedBox(height: 16),
+            Row(
+              children: [
+                Text('Prezzo netto: ', style: theme.textTheme.bodyLarge),
+                Text(
+                  _prezzoNetto.toStringAsFixed(2),
+                  style: theme.textTheme.bodyLarge?.copyWith(
+                    fontWeight: FontWeight.bold,
+                  ),
+                ),
+              ],
+            ),
+            const SizedBox(height: 12),
+            Row(
+              children: [
+                Expanded(
+                  child: TextField(
+                    controller: _colliCtrl,
+                    decoration: const InputDecoration(labelText: 'Colli'),
+                    keyboardType: TextInputType.number,
+                    onChanged: _onColliChanged,
+                  ),
+                ),
+                const SizedBox(width: 12),
+                Expanded(
+                  child: TextField(
+                    controller: _qtaCtrl,
+                    decoration: const InputDecoration(labelText: 'Quantità'),
+                    keyboardType: TextInputType.number,
+                    onChanged: _onQtaChanged,
+                  ),
+                ),
+              ],
+            ),
+            const SizedBox(height: 20),
+            SizedBox(
+              width: double.infinity,
+              child: ElevatedButton(
+                onPressed: _confirm,
+                child: const Text('Aggiungi'),
+              ),
+            ),
+            const SizedBox(height: 8),
+          ],
+        ),
+      ),
+    );
+  }
+}

+ 68 - 53
lib/ui/pages/sync/sync_page.dart

@@ -36,61 +36,76 @@ class _SyncPageState extends ConsumerState<SyncPage> {
           child: Padding(
             padding: const EdgeInsets.all(32),
             child: Column(
-            mainAxisSize: MainAxisSize.min,
-            children: [
-              Icon(
-                syncState.isSyncing ? Icons.sync : Icons.sync_outlined,
-                size: 64,
-                color: syncState.isSyncing ? theme.colorScheme.primary : theme.colorScheme.outline,
-              ),
-              const SizedBox(height: 24),
-              Text(
-                syncState.isSyncing ? 'Sincronizzazione in corso...' : 'Sincronizzazione dati',
-                style: theme.textTheme.titleLarge,
-              ),
-              const SizedBox(height: 16),
-              if (syncState.isSyncing) ...[
-                LinearProgressIndicator(value: syncState.progress),
-                const SizedBox(height: 12),
-                Text(syncState.message, style: theme.textTheme.bodyMedium),
-                const SizedBox(height: 8),
-                Text('${(syncState.progress * 100).toInt()}%', style: theme.textTheme.bodySmall),
-              ] else if (syncState.error != null) ...[
-                Card(
-                  color: theme.colorScheme.errorContainer,
-                  child: Padding(
-                    padding: const EdgeInsets.all(12),
-                    child: Text(syncState.error!, style: TextStyle(color: theme.colorScheme.onErrorContainer)),
-                  ),
+              mainAxisSize: MainAxisSize.min,
+              children: [
+                Icon(
+                  syncState.isSyncing ? Icons.sync : Icons.sync_outlined,
+                  size: 64,
+                  color: syncState.isSyncing
+                      ? theme.colorScheme.primary
+                      : theme.colorScheme.outline,
                 ),
-              ] else if (syncState.progress >= 1.0) ...[
-                Icon(Icons.check_circle, size: 48, color: Colors.green),
-                const SizedBox(height: 8),
-                const Text('Completato!'),
-              ],
-              const SizedBox(height: 12),
-              CheckboxListTile(
-                value: _forceFullSync,
-                onChanged: syncState.isSyncing ? null : (v) => setState(() => _forceFullSync = v!),
-                title: const Text('Sincronizzazione completa'),
-                subtitle: const Text('Forza il download completo di tutti i dati'),
-                controlAffinity: ListTileControlAffinity.leading,
-                enabled: !syncState.isSyncing,
-              ),
-              const SizedBox(height: 12),
-              FilledButton.icon(
-                onPressed: syncState.isSyncing || agente == null
-                    ? null
-                    : () => ref.read(syncProvider.notifier).startFullSync(
-                      agente.idAgente,
-                      agente.codAgente,
-                      forceFullSync: _forceFullSync,
+                const SizedBox(height: 24),
+                Text(
+                  syncState.isSyncing
+                      ? 'Sincronizzazione in corso...'
+                      : 'Sincronizzazione dati',
+                  style: theme.textTheme.titleLarge,
+                ),
+                const SizedBox(height: 16),
+                if (syncState.isSyncing) ...[
+                  LinearProgressIndicator(value: syncState.progress),
+                  const SizedBox(height: 12),
+                  Text(syncState.message, style: theme.textTheme.bodyMedium),
+                  const SizedBox(height: 8),
+                  Text('${(syncState.progress * 100).toInt()}%',
+                      style: theme.textTheme.bodySmall),
+                ] else if (syncState.error != null) ...[
+                  Card(
+                    color: theme.colorScheme.errorContainer,
+                    child: Padding(
+                      padding: const EdgeInsets.all(12),
+                      child: Text(syncState.error!,
+                          style: TextStyle(
+                              color: theme.colorScheme.onErrorContainer)),
                     ),
-                icon: syncState.isSyncing
-                    ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))
-                    : const Icon(Icons.sync),
-                label: Text(syncState.isSyncing ? 'In corso...' : 'Avvia Sincronizzazione'),
-              ),
+                  ),
+                ] else if (syncState.progress >= 1.0) ...[
+                  Icon(Icons.check_circle, size: 48, color: Colors.green),
+                  const SizedBox(height: 8),
+                  const Text('Completato!'),
+                ],
+                const SizedBox(height: 12),
+                CheckboxListTile(
+                  value: _forceFullSync,
+                  onChanged: syncState.isSyncing
+                      ? null
+                      : (v) => setState(() => _forceFullSync = v!),
+                  title: const Text('Sincronizzazione completa'),
+                  subtitle:
+                      const Text('Forza il download completo di tutti i dati'),
+                  controlAffinity: ListTileControlAffinity.leading,
+                  enabled: !syncState.isSyncing,
+                ),
+                const SizedBox(height: 12),
+                FilledButton.icon(
+                  onPressed: syncState.isSyncing || agente == null
+                      ? null
+                      : () => ref.read(syncProvider.notifier).startFullSync(
+                            agente.idAgente,
+                            agente.codAgente!,
+                            forceFullSync: _forceFullSync,
+                          ),
+                  icon: syncState.isSyncing
+                      ? const SizedBox(
+                          width: 20,
+                          height: 20,
+                          child: CircularProgressIndicator(strokeWidth: 2))
+                      : const Icon(Icons.sync),
+                  label: Text(syncState.isSyncing
+                      ? 'In corso...'
+                      : 'Avvia Sincronizzazione'),
+                ),
               ],
             ),
           ),

+ 55 - 0
pubspec copy.yaml

@@ -0,0 +1,55 @@
+name: ristoris
+description: "PowerAge Ristoris - Order management app"
+publish_to: 'none'
+version: 1.0.0+1
+
+environment:
+  sdk: '>=3.0.0 <4.0.0'
+
+dependencies:
+  flutter:
+    sdk: flutter
+  cupertino_icons: ^1.0.8
+
+  # Database
+  drift: ^2.32.0
+  sqlite3_flutter_libs: ^0.5.20
+  sqlite3: ^3.0.0
+  path_provider: ^2.1.0
+  path: ^1.9.0
+
+  # State management
+  flutter_riverpod: ^2.5.1
+  #riverpod_annotation: ^4.0.2
+
+  # Routing
+  go_router: ^17.3.0
+
+  # Networking
+  http: ^1.2.1
+
+  # JSON serialization
+  freezed_annotation: ^3.1.0
+  json_annotation: ^4.9.0
+
+  # UI
+  cached_network_image: ^3.4.0
+  intl: ^0.20.3
+  fl_chart: ^1.2.0
+  connectivity_plus: ^7.2.0
+  google_fonts: ^8.1.0
+
+dev_dependencies:
+  #flutter_test:
+  #  sdk: flutter
+  flutter_lints: ^6.0.0
+  drift_dev: '>=2.32.0 <2.34.0'
+  freezed: ^3.2.5
+  json_serializable: ^6.8.0
+  # riverpod_generator: ^4.0.3
+  build_runner: ^2.4.0
+
+flutter:
+  uses-material-design: true
+  assets:
+    - assets/db/

+ 1 - 1
pubspec.yaml

@@ -4,7 +4,7 @@ publish_to: 'none'
 version: 1.0.0+1
 
 environment:
-  sdk: ^3.11.0
+  sdk: '>=3.0.0 <4.0.0'
 
 dependencies:
   flutter:

Файловите разлики са ограничени, защото са твърде много
+ 3105 - 0
web/drift_worker.dart.js


Файловите разлики са ограничени, защото са твърде много
+ 3106 - 0
web/drift_worker.js


BIN
web/sqlite3.wasm


Някои файлове не бяха показани, защото твърде много файлове са промени