|
@@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
|
|
|
import 'package:flutter/material.dart';
|
|
|
import 'package:flutter/services.dart';
|
|
|
import 'package:intl/intl.dart';
|
|
|
+import 'package:otp/otp.dart';
|
|
|
import 'package:provider/provider.dart';
|
|
|
import '/data/session/session_storage.dart';
|
|
|
import '/views/pedido/pedido_ticket.dart';
|
|
@@ -13,6 +14,8 @@ import '../../viewmodels/viewmodels.dart';
|
|
|
import 'package:collection/collection.dart';
|
|
|
import '../../widgets/widgets.dart';
|
|
|
import '../../services/services.dart';
|
|
|
+import 'package:timezone/data/latest.dart' as timezone;
|
|
|
+import 'package:timezone/timezone.dart' as timezone;
|
|
|
|
|
|
class PedidoForm extends StatefulWidget {
|
|
|
final Pedido? pedidoExistente;
|
|
@@ -346,6 +349,15 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
TextButton(
|
|
|
onPressed: () => Navigator.of(context).pop(false),
|
|
|
child: const Text('Cancelar'),
|
|
|
+ style: ButtonStyle(
|
|
|
+ padding: MaterialStatePropertyAll(
|
|
|
+ EdgeInsets.fromLTRB(20, 10, 20, 10)),
|
|
|
+ backgroundColor: MaterialStatePropertyAll(Colors.red),
|
|
|
+ foregroundColor:
|
|
|
+ MaterialStatePropertyAll(AppTheme.secondary)),
|
|
|
+ ),
|
|
|
+ const SizedBox(
|
|
|
+ width: 10,
|
|
|
),
|
|
|
TextButton(
|
|
|
onPressed: () {
|
|
@@ -360,6 +372,12 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
}
|
|
|
},
|
|
|
child: const Text('Guardar'),
|
|
|
+ style: ButtonStyle(
|
|
|
+ padding: MaterialStatePropertyAll(
|
|
|
+ EdgeInsets.fromLTRB(20, 10, 20, 10)),
|
|
|
+ backgroundColor: MaterialStatePropertyAll(Colors.black),
|
|
|
+ foregroundColor:
|
|
|
+ MaterialStatePropertyAll(AppTheme.quaternary)),
|
|
|
),
|
|
|
],
|
|
|
);
|
|
@@ -434,8 +452,11 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
);
|
|
|
|
|
|
if (result) {
|
|
|
+ Pedido? pedidoCompleto =
|
|
|
+ await Provider.of<PedidoViewModel>(context, listen: false)
|
|
|
+ .fetchPedidoConProductos(pedidoActual!.id);
|
|
|
if (estatus == "TERMINADO") {
|
|
|
- imprimirTicketsJuntos(context, pedidoActual!);
|
|
|
+ imprimirTicketsJuntos(context, pedidoCompleto!);
|
|
|
}
|
|
|
print("Pedido actualizado correctamente");
|
|
|
Navigator.of(context).pop();
|
|
@@ -1295,6 +1316,10 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
},
|
|
|
),
|
|
|
),
|
|
|
+ if (pedidoActual != null && pedidoActual!.id! > 0)
|
|
|
+ _buildMesaSelector(),
|
|
|
+ if (pedidoActual != null && pedidoActual!.id! > 0)
|
|
|
+ const SizedBox(height: 5),
|
|
|
_buildDiscountSection(),
|
|
|
const Divider(thickness: 5),
|
|
|
_buildTotalSection(),
|
|
@@ -1330,7 +1355,7 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
),
|
|
|
),
|
|
|
] else ...[
|
|
|
- if (_isMesasActive) ...[
|
|
|
+ if (_isMesasActive)
|
|
|
ElevatedButton(
|
|
|
onPressed: () => _crearPedidoConModal(),
|
|
|
style: ElevatedButton.styleFrom(
|
|
@@ -1339,24 +1364,23 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
fixedSize: const Size(250, 50),
|
|
|
),
|
|
|
child: Text(
|
|
|
- 'Crear Pedido',
|
|
|
+ 'Iniciar Pedido',
|
|
|
style: TextStyle(color: AppTheme.quaternary),
|
|
|
),
|
|
|
),
|
|
|
- ] else ...[
|
|
|
- ElevatedButton(
|
|
|
- onPressed: () => _promptForCustomerName(),
|
|
|
- style: ElevatedButton.styleFrom(
|
|
|
- backgroundColor: AppTheme.tertiary,
|
|
|
- textStyle: const TextStyle(fontSize: 22),
|
|
|
- fixedSize: const Size(250, 50),
|
|
|
- ),
|
|
|
- child: Text(
|
|
|
- 'Finalizar Pedido',
|
|
|
- style: TextStyle(color: AppTheme.quaternary),
|
|
|
- ),
|
|
|
+ const SizedBox(height: 5),
|
|
|
+ ElevatedButton(
|
|
|
+ onPressed: () => _promptForCustomerName(),
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
+ backgroundColor: AppTheme.rojo,
|
|
|
+ textStyle: const TextStyle(fontSize: 18),
|
|
|
+ fixedSize: const Size(250, 50),
|
|
|
+ ),
|
|
|
+ child: Text(
|
|
|
+ 'Finalizar Pedido Sin Mesa',
|
|
|
+ style: TextStyle(color: AppTheme.quaternary),
|
|
|
),
|
|
|
- ]
|
|
|
+ ),
|
|
|
]
|
|
|
],
|
|
|
),
|
|
@@ -1366,11 +1390,42 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- void eliminarProductoDelCarrito(int index) {
|
|
|
- setState(() {
|
|
|
- carrito.removeAt(index);
|
|
|
- });
|
|
|
- _recalcularTotal();
|
|
|
+ void eliminarProductoDelCarrito(int index) async {
|
|
|
+ bool autorizado = true;
|
|
|
+
|
|
|
+ // Solicitar autenticación solo si el pedido tiene un ID mayor a 0
|
|
|
+ if (pedidoActual != null && pedidoActual!.id! > 0) {
|
|
|
+ autorizado = await autenticarConCodigo(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (autorizado) {
|
|
|
+ final producto = carrito[index].producto;
|
|
|
+
|
|
|
+ // Verificar si el pedido actual tiene un ID mayor a 0
|
|
|
+ if (pedidoActual != null && pedidoActual!.id! > 0) {
|
|
|
+ final pedidoProducto = pedidoActual!.productos
|
|
|
+ .firstWhereOrNull((p) => p.idProducto == producto.id);
|
|
|
+
|
|
|
+ if (pedidoProducto != null) {
|
|
|
+ // Marcar como eliminado en la base de datos
|
|
|
+ await Provider.of<PedidoViewModel>(context, listen: false)
|
|
|
+ .eliminarProductoDelPedido(producto.id!, pedidoActual!.id!);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Remover el producto del carrito en la UI
|
|
|
+ setState(() {
|
|
|
+ carrito.removeAt(index);
|
|
|
+ });
|
|
|
+
|
|
|
+ // Recalcular el total
|
|
|
+ _recalcularTotal();
|
|
|
+
|
|
|
+ // Mostrar mensaje de confirmación
|
|
|
+ ScaffoldMessenger.of(context).showSnackBar(
|
|
|
+ const SnackBar(content: Text("Producto eliminado del carrito.")),
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
void incrementarProducto(ItemCarrito item) {
|
|
@@ -1380,15 +1435,44 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
_recalcularTotal();
|
|
|
}
|
|
|
|
|
|
- void quitarProductoDelCarrito(ItemCarrito item) {
|
|
|
- setState(() {
|
|
|
- if (item.cantidad > 1) {
|
|
|
- item.cantidad--;
|
|
|
- } else {
|
|
|
- carrito.remove(item);
|
|
|
- }
|
|
|
- });
|
|
|
- _recalcularTotal();
|
|
|
+ void quitarProductoDelCarrito(ItemCarrito item) async {
|
|
|
+ bool autorizado = true;
|
|
|
+
|
|
|
+ if (pedidoActual != null && pedidoActual!.id! > 0) {
|
|
|
+ autorizado = await autenticarConCodigo(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (autorizado) {
|
|
|
+ setState(() {
|
|
|
+ if (item.cantidad > 1) {
|
|
|
+ item.cantidad--;
|
|
|
+ } else {
|
|
|
+ if (pedidoActual != null && pedidoActual!.id! > 0) {
|
|
|
+ final pedidoProducto = pedidoActual!.productos
|
|
|
+ .firstWhereOrNull((p) => p.idProducto == item.producto.id);
|
|
|
+
|
|
|
+ if (pedidoProducto != null) {
|
|
|
+ Provider.of<PedidoViewModel>(context, listen: false)
|
|
|
+ .eliminarProductoDelPedido(
|
|
|
+ item.producto.id!, pedidoActual!.id!);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ carrito.remove(item);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ _recalcularTotal();
|
|
|
+
|
|
|
+ // Mostrar mensaje de confirmación
|
|
|
+ ScaffoldMessenger.of(context).showSnackBar(
|
|
|
+ SnackBar(
|
|
|
+ content: Text(item.cantidad > 0
|
|
|
+ ? "Cantidad del producto actualizada."
|
|
|
+ : "Producto marcado como eliminado."),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
Widget _buildProductsSection() {
|
|
@@ -1542,4 +1626,152 @@ class _PedidoFormState extends State<PedidoForm> {
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
+
|
|
|
+ Future<bool> autenticarConCodigo(BuildContext context) async {
|
|
|
+ final TextEditingController codeController = TextEditingController();
|
|
|
+
|
|
|
+ return await showDialog<bool>(
|
|
|
+ context: context,
|
|
|
+ builder: (context) {
|
|
|
+ return AlertDialog(
|
|
|
+ title: const Text("Autenticación requerida",
|
|
|
+ style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22)),
|
|
|
+ content: Column(
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ children: [
|
|
|
+ const Text(
|
|
|
+ "Por favor, introduce el código de autenticación generado.",
|
|
|
+ style: TextStyle(fontSize: 18),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 16),
|
|
|
+ TextField(
|
|
|
+ controller: codeController,
|
|
|
+ decoration: const InputDecoration(
|
|
|
+ labelText: "Código de autenticación",
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ actions: [
|
|
|
+ Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
+ children: [
|
|
|
+ TextButton(
|
|
|
+ onPressed: () => Navigator.of(context).pop(false),
|
|
|
+ child: Text('Cancelar',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 18, color: AppTheme.secondary)),
|
|
|
+ style: ButtonStyle(
|
|
|
+ padding: MaterialStatePropertyAll(
|
|
|
+ EdgeInsets.fromLTRB(20, 10, 20, 10)),
|
|
|
+ backgroundColor: MaterialStatePropertyAll(Colors.red),
|
|
|
+ foregroundColor:
|
|
|
+ MaterialStatePropertyAll(AppTheme.secondary)),
|
|
|
+ ),
|
|
|
+ TextButton(
|
|
|
+ onPressed: () {
|
|
|
+ final now = DateTime.now().toUtc();
|
|
|
+ timezone.initializeTimeZones();
|
|
|
+ final pacificTimeZone =
|
|
|
+ timezone.getLocation('America/Los_Angeles');
|
|
|
+ final date =
|
|
|
+ timezone.TZDateTime.from(now, pacificTimeZone);
|
|
|
+
|
|
|
+ final codigoTotp = OTP.generateTOTPCodeString(
|
|
|
+ 'TYSNE4CMT5LVLGWS',
|
|
|
+ date.millisecondsSinceEpoch,
|
|
|
+ algorithm: Algorithm.SHA1,
|
|
|
+ isGoogle: true,
|
|
|
+ );
|
|
|
+
|
|
|
+ String codigo = codeController.text.trim();
|
|
|
+ List<String> codigosEstaticos = [
|
|
|
+ '172449',
|
|
|
+ '827329',
|
|
|
+ // Agregar más códigos estáticos si es necesario
|
|
|
+ ];
|
|
|
+
|
|
|
+ bool esCodigoValido =
|
|
|
+ codigosEstaticos.contains(codigo) ||
|
|
|
+ codigo == codigoTotp;
|
|
|
+
|
|
|
+ if (!esCodigoValido) {
|
|
|
+ ScaffoldMessenger.of(context).showSnackBar(
|
|
|
+ const SnackBar(
|
|
|
+ content: Text('El código no es correcto'),
|
|
|
+ duration: Duration(seconds: 2),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Navigator.of(context).pop(true);
|
|
|
+ },
|
|
|
+ child: Text('Confirmar',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 18, color: AppTheme.quaternary)),
|
|
|
+ style: ButtonStyle(
|
|
|
+ padding: MaterialStatePropertyAll(
|
|
|
+ EdgeInsets.fromLTRB(20, 10, 20, 10)),
|
|
|
+ backgroundColor:
|
|
|
+ MaterialStatePropertyAll(AppTheme.secondary),
|
|
|
+ foregroundColor:
|
|
|
+ MaterialStatePropertyAll(AppTheme.secondary)),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ )
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ) ??
|
|
|
+ false;
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildMesaSelector() {
|
|
|
+ return FutureBuilder(
|
|
|
+ future:
|
|
|
+ Provider.of<MesaViewModel>(context, listen: false).fetchLocalAll(),
|
|
|
+ builder: (context, snapshot) {
|
|
|
+ if (snapshot.connectionState == ConnectionState.waiting) {
|
|
|
+ return const Center(child: CircularProgressIndicator());
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Mesa> mesasDisponibles =
|
|
|
+ Provider.of<MesaViewModel>(context, listen: false).mesas;
|
|
|
+
|
|
|
+ return Padding(
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
|
|
+ child: Row(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ const Text(
|
|
|
+ 'Mesa:',
|
|
|
+ style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 16),
|
|
|
+ Expanded(
|
|
|
+ child: DropdownButtonFormField<int>(
|
|
|
+ value: pedidoActual?.idMesa,
|
|
|
+ items: mesasDisponibles.map((mesa) {
|
|
|
+ return DropdownMenuItem<int>(
|
|
|
+ value: mesa.id,
|
|
|
+ child: Text(mesa.nombre ?? 'Sin Nombre'),
|
|
|
+ );
|
|
|
+ }).toList(),
|
|
|
+ onChanged: (int? nuevaMesaId) {
|
|
|
+ setState(() {
|
|
|
+ pedidoActual?.idMesa = nuevaMesaId;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ decoration: const InputDecoration(
|
|
|
+ border: OutlineInputBorder(),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|