pedido_detalle_screen.dart 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. import 'package:collection/collection.dart';
  2. import 'package:intl/intl.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:provider/provider.dart';
  5. import '../../models/models.dart';
  6. import '../../services/services.dart';
  7. import '../../themes/themes.dart';
  8. import '../../viewmodels/viewmodels.dart';
  9. import '../../widgets/widgets.dart';
  10. import '../pedido/pedido_ticket.dart';
  11. class PedidoDetalleScreen extends StatefulWidget {
  12. final Pedido pedido;
  13. PedidoDetalleScreen({Key? key, required this.pedido}) : super(key: key);
  14. @override
  15. _PedidoDetalleScreenState createState() => _PedidoDetalleScreenState();
  16. }
  17. class _PedidoDetalleScreenState extends State<PedidoDetalleScreen> {
  18. late Pedido pedido;
  19. @override
  20. void initState() {
  21. super.initState();
  22. pedido = widget.pedido;
  23. }
  24. String formatCurrency(double amount) {
  25. final format = NumberFormat("#,##0.00", "es_MX");
  26. return format.format(amount);
  27. }
  28. final List<String> tiposDePago = ['Efectivo', 'Tarjeta', 'Transferencia'];
  29. @override
  30. Widget build(BuildContext context) {
  31. double totalSinDescuento =
  32. pedido.productos.fold(0, (previousValue, element) {
  33. double productTotal =
  34. element.cantidad! * (element.producto?.precio ?? 0.0);
  35. double toppingsTotal = element.toppings.fold(0, (toppingTotal, topping) {
  36. return toppingTotal +
  37. (topping.topping?.precio ?? 0.0) * element.cantidad!;
  38. });
  39. return previousValue + productTotal + toppingsTotal;
  40. });
  41. double descuento = pedido.descuento?.toDouble() ?? 0.0;
  42. double precioDescuento = totalSinDescuento * (descuento / 100);
  43. double totalConDescuento = totalSinDescuento - precioDescuento;
  44. return Scaffold(
  45. appBar: AppBar(
  46. title: Text(
  47. 'Detalle del Pedido ${pedido.folio}',
  48. style: TextStyle(fontWeight: FontWeight.w500),
  49. ),
  50. ),
  51. body: SingleChildScrollView(
  52. padding: const EdgeInsets.all(12.0),
  53. child: Column(
  54. children: [
  55. Card(
  56. elevation: 4,
  57. color: Colors.white,
  58. child: Column(
  59. children: [
  60. // Colocamos la fecha y el cliente en una misma fila
  61. ListTile(
  62. title: Row(
  63. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  64. children: [
  65. Text(
  66. 'Cliente: ${pedido.nombreCliente}',
  67. style: TextStyle(
  68. fontWeight: FontWeight.bold, fontSize: 22),
  69. ),
  70. Text(
  71. 'Fecha: ${_formatDateTime(pedido.peticion)}',
  72. style: TextStyle(
  73. fontWeight: FontWeight.bold, fontSize: 22),
  74. ),
  75. ],
  76. ),
  77. ),
  78. ListTile(
  79. subtitle: Text(
  80. 'Comentarios: ${pedido.comentarios}',
  81. style:
  82. TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
  83. ),
  84. ),
  85. ListTile(
  86. title: Text(
  87. 'Estado del Pedido: ${pedido.estatus}',
  88. style: TextStyle(
  89. fontSize: 22,
  90. fontWeight: FontWeight.bold,
  91. ),
  92. ),
  93. ),
  94. ],
  95. ),
  96. ),
  97. SizedBox(height: 10),
  98. Card(
  99. elevation: 4,
  100. color: Colors.white,
  101. child: Padding(
  102. padding: const EdgeInsets.all(8.0),
  103. child: Column(
  104. crossAxisAlignment: CrossAxisAlignment.start,
  105. children: [
  106. Text('Productos',
  107. style: TextStyle(
  108. fontSize: 22, fontWeight: FontWeight.bold)),
  109. const SizedBox(height: 15),
  110. ListView.builder(
  111. shrinkWrap: true,
  112. physics: NeverScrollableScrollPhysics(),
  113. itemCount: pedido.productos.length,
  114. itemBuilder: (context, index) {
  115. final producto = pedido.productos[index];
  116. return Padding(
  117. padding: const EdgeInsets.symmetric(vertical: 4.0),
  118. child: Column(
  119. crossAxisAlignment: CrossAxisAlignment.start,
  120. children: [
  121. Row(
  122. children: [
  123. Expanded(
  124. flex: 6,
  125. child: Text(
  126. producto.producto?.nombre ??
  127. "Producto no especificado",
  128. style: TextStyle(
  129. fontWeight: FontWeight.bold,
  130. fontSize: 17),
  131. overflow: TextOverflow.ellipsis,
  132. ),
  133. ),
  134. Expanded(
  135. flex: 1,
  136. child: Text(
  137. 'x${producto.cantidad}',
  138. style: TextStyle(
  139. fontWeight: FontWeight.w500,
  140. fontSize: 17),
  141. textAlign: TextAlign.center,
  142. ),
  143. ),
  144. Expanded(
  145. flex: 2,
  146. child: Text(
  147. '\$${formatCurrency(producto.producto?.precio ?? 0.0)}',
  148. style: TextStyle(
  149. fontWeight: FontWeight.w500,
  150. fontSize: 17),
  151. textAlign: TextAlign.right,
  152. ),
  153. ),
  154. ],
  155. ),
  156. if (producto.comentario!.isNotEmpty)
  157. Text(
  158. 'Comentarios: ${producto.comentario!}',
  159. style: TextStyle(
  160. fontSize: 15, color: AppTheme.tertiary),
  161. ),
  162. if (producto.toppings.isNotEmpty)
  163. Padding(
  164. padding: const EdgeInsets.only(top: 4.0),
  165. child: Column(
  166. crossAxisAlignment:
  167. CrossAxisAlignment.start,
  168. children: producto.toppings.map((topping) {
  169. return Padding(
  170. padding: const EdgeInsets.symmetric(
  171. vertical: 2.0),
  172. child: Row(
  173. children: [
  174. Text(
  175. '- ${topping.topping?.nombre ?? "Topping no especificado"}',
  176. style: TextStyle(
  177. fontSize: 15,
  178. color: Colors.grey[600]),
  179. ),
  180. Spacer(),
  181. Text(
  182. '\$${formatCurrency(topping.topping?.precio ?? 0.0)}',
  183. style: TextStyle(
  184. fontSize: 15,
  185. color: Colors.grey[600]),
  186. ),
  187. ],
  188. ),
  189. );
  190. }).toList(),
  191. ),
  192. ),
  193. ],
  194. ),
  195. );
  196. },
  197. ),
  198. Divider(),
  199. Padding(
  200. padding: const EdgeInsets.symmetric(vertical: 8.0),
  201. child: Column(
  202. crossAxisAlignment: CrossAxisAlignment.end,
  203. children: [
  204. Row(
  205. mainAxisAlignment: MainAxisAlignment.end,
  206. children: [
  207. const Text('Subtotal:',
  208. style: TextStyle(
  209. fontSize: 16,
  210. fontWeight: FontWeight.bold)),
  211. const SizedBox(width: 5),
  212. Text('\$${formatCurrency(totalSinDescuento)}',
  213. style: const TextStyle(
  214. fontSize: 16,
  215. fontWeight: FontWeight.bold)),
  216. ],
  217. ),
  218. if (descuento > 0) ...[
  219. Row(
  220. mainAxisAlignment: MainAxisAlignment.end,
  221. children: [
  222. Text(
  223. 'Descuento (${descuento.toStringAsFixed(0)}%):',
  224. style: const TextStyle(
  225. fontSize: 16,
  226. fontWeight: FontWeight.bold)),
  227. const SizedBox(width: 8),
  228. Text('-\$${formatCurrency(precioDescuento)}',
  229. style: const TextStyle(
  230. fontSize: 16,
  231. fontWeight: FontWeight.bold)),
  232. ],
  233. ),
  234. ],
  235. Row(
  236. mainAxisAlignment: MainAxisAlignment.end,
  237. children: [
  238. const Text('Total:',
  239. style: TextStyle(
  240. fontSize: 16,
  241. fontWeight: FontWeight.bold)),
  242. const SizedBox(width: 5),
  243. Text('\$${formatCurrency(totalConDescuento)}',
  244. style: const TextStyle(
  245. fontSize: 16,
  246. fontWeight: FontWeight.bold)),
  247. ],
  248. ),
  249. ],
  250. ),
  251. ),
  252. ],
  253. ),
  254. ),
  255. ),
  256. const SizedBox(height: 10),
  257. Card(
  258. elevation: 4,
  259. color: Colors.white,
  260. child: Padding(
  261. padding: const EdgeInsets.all(8.0),
  262. child: Consumer<PedidoViewModel>(
  263. builder: (context, viewModel, _) {
  264. return Column(
  265. crossAxisAlignment: CrossAxisAlignment.start,
  266. children: [
  267. Row(
  268. children: [
  269. Text('Pago',
  270. style: TextStyle(
  271. fontSize: 22, fontWeight: FontWeight.bold)),
  272. Spacer(),
  273. ElevatedButton(
  274. onPressed: () {
  275. showDialog(
  276. context: context,
  277. builder: (context) {
  278. return TotpCuadroConfirmacion(
  279. title: "Cambiar Método de Pago",
  280. content:
  281. "Por favor, ingresa el código de autenticación para continuar.",
  282. onSuccess: () {
  283. _mostrarModalCambiarMetodoPago(context);
  284. },
  285. );
  286. },
  287. );
  288. },
  289. child: Text('Cambiar Método Pago',
  290. style: TextStyle(
  291. color: AppTheme.quaternary,
  292. fontWeight: FontWeight.w500,
  293. fontSize: 16)),
  294. style: ElevatedButton.styleFrom(
  295. backgroundColor: AppTheme.tertiary,
  296. padding:
  297. const EdgeInsets.fromLTRB(20, 10, 20, 10),
  298. ),
  299. ),
  300. ],
  301. ),
  302. const SizedBox(height: 10),
  303. if ((pedido.cantEfectivo ?? 0) > 0)
  304. _buildReadOnlyPaymentRow(
  305. "Efectivo", pedido.cantEfectivo ?? 0.0),
  306. if ((pedido.cantTarjeta ?? 0) > 0)
  307. _buildReadOnlyPaymentRow(
  308. "Tarjeta", pedido.cantTarjeta ?? 0.0),
  309. if ((pedido.cantTransferencia ?? 0) > 0)
  310. _buildReadOnlyPaymentRow(
  311. "Transferencia", pedido.cantTransferencia ?? 0.0),
  312. ],
  313. );
  314. },
  315. ),
  316. ),
  317. ),
  318. const SizedBox(height: 20),
  319. Align(
  320. alignment: Alignment.centerLeft,
  321. child: ElevatedButton.icon(
  322. icon: Icon(
  323. Icons.receipt_long_outlined,
  324. color: AppTheme.quaternary,
  325. size: 30,
  326. ),
  327. onPressed: () => imprimirTicketsJuntos(context, pedido),
  328. label: Text(
  329. 'Imprimir Ticket',
  330. style: TextStyle(
  331. fontWeight: FontWeight.w500,
  332. fontSize: 18,
  333. color: AppTheme.quaternary),
  334. ),
  335. style: ElevatedButton.styleFrom(
  336. padding: const EdgeInsets.fromLTRB(50, 20, 50, 20),
  337. backgroundColor: AppTheme.tertiary,
  338. ),
  339. ),
  340. )
  341. ],
  342. ),
  343. ),
  344. );
  345. }
  346. Widget _buildReadOnlyPaymentRow(String paymentType, double amount) {
  347. return Padding(
  348. padding: const EdgeInsets.symmetric(vertical: 6.0),
  349. child: Row(
  350. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  351. children: [
  352. Text(paymentType,
  353. style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
  354. Text('\$${formatCurrency(amount)}',
  355. style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
  356. ],
  357. ),
  358. );
  359. }
  360. Future<void> _mostrarModalCambiarMetodoPago(BuildContext context) async {
  361. double totalPedido = pedido.totalPedido ?? 0.0;
  362. TextEditingController efectivoController = TextEditingController(
  363. text: (pedido.cantEfectivo ?? 0) > 0
  364. ? pedido.cantEfectivo!.toStringAsFixed(2)
  365. : '');
  366. TextEditingController tarjetaController = TextEditingController(
  367. text: (pedido.cantTarjeta ?? 0) > 0
  368. ? pedido.cantTarjeta!.toStringAsFixed(2)
  369. : '');
  370. TextEditingController transferenciaController = TextEditingController(
  371. text: (pedido.cantTransferencia ?? 0) > 0
  372. ? pedido.cantTransferencia!.toStringAsFixed(2)
  373. : '');
  374. bool efectivoSeleccionado = (pedido.cantEfectivo ?? 0) > 0;
  375. bool tarjetaSeleccionada = (pedido.cantTarjeta ?? 0) > 0;
  376. bool transferenciaSeleccionada = (pedido.cantTransferencia ?? 0) > 0;
  377. bool efectivoCompleto = false;
  378. bool tarjetaCompleto = false;
  379. bool transferenciaCompleto = false;
  380. double cambio = 0.0;
  381. double faltante = totalPedido;
  382. bool totalCompletado = false;
  383. void _calcularCambio(StateSetter setState) {
  384. double totalPagado = (double.tryParse(efectivoController.text) ?? 0) +
  385. (double.tryParse(tarjetaController.text) ?? 0) +
  386. (double.tryParse(transferenciaController.text) ?? 0);
  387. setState(() {
  388. cambio = totalPagado - totalPedido;
  389. faltante = cambio < 0 ? totalPedido - totalPagado : 0;
  390. totalCompletado = cambio >= 0;
  391. });
  392. }
  393. bool? shouldSave = await showDialog<bool>(
  394. context: context,
  395. builder: (BuildContext context) {
  396. return StatefulBuilder(
  397. builder: (context, setState) {
  398. return AlertDialog(
  399. actionsPadding: EdgeInsets.fromLTRB(50, 10, 50, 30),
  400. title: const Text(
  401. 'Cambiar Método de Pago',
  402. style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
  403. ),
  404. content: SingleChildScrollView(
  405. child: AnimatedSize(
  406. duration: const Duration(milliseconds: 300),
  407. curve: Curves.easeInOut,
  408. child: Column(
  409. crossAxisAlignment: CrossAxisAlignment.stretch,
  410. children: [
  411. Align(
  412. alignment: Alignment.center,
  413. child: Text(
  414. 'Métodos de pago',
  415. style: TextStyle(
  416. fontWeight: FontWeight.bold, fontSize: 20),
  417. ),
  418. ),
  419. const SizedBox(height: 10),
  420. _buildPaymentMethodRow(
  421. setState,
  422. totalPedido,
  423. label: 'Efectivo',
  424. selected: efectivoSeleccionado,
  425. exactSelected: efectivoCompleto,
  426. controller: efectivoController,
  427. onSelected: (value) {
  428. setState(() {
  429. efectivoSeleccionado = value;
  430. if (!efectivoSeleccionado) {
  431. efectivoCompleto = false;
  432. efectivoController.clear();
  433. }
  434. _calcularCambio(setState);
  435. });
  436. },
  437. onExactSelected: (value) {
  438. setState(() {
  439. efectivoCompleto = value;
  440. if (efectivoCompleto) {
  441. efectivoController.text =
  442. totalPedido.toStringAsFixed(2);
  443. efectivoSeleccionado = true;
  444. tarjetaSeleccionada = false;
  445. transferenciaSeleccionada = false;
  446. tarjetaController.clear();
  447. transferenciaController.clear();
  448. } else {
  449. efectivoController.clear();
  450. }
  451. _calcularCambio(setState);
  452. });
  453. },
  454. disableOtherMethods:
  455. tarjetaCompleto || transferenciaCompleto,
  456. onChangedMonto: () => _calcularCambio(setState),
  457. ),
  458. _buildPaymentMethodRow(
  459. setState,
  460. totalPedido,
  461. label: 'Tarjeta',
  462. selected: tarjetaSeleccionada,
  463. exactSelected: tarjetaCompleto,
  464. controller: tarjetaController,
  465. sinCambio: true,
  466. onSelected: (value) {
  467. setState(() {
  468. tarjetaSeleccionada = value;
  469. if (!tarjetaSeleccionada) {
  470. tarjetaCompleto = false;
  471. tarjetaController.clear();
  472. }
  473. _calcularCambio(setState);
  474. });
  475. },
  476. onExactSelected: (value) {
  477. setState(() {
  478. tarjetaCompleto = value;
  479. if (tarjetaCompleto) {
  480. tarjetaController.text =
  481. totalPedido.toStringAsFixed(2);
  482. tarjetaSeleccionada = true;
  483. efectivoSeleccionado = false;
  484. transferenciaSeleccionada = false;
  485. efectivoController.clear();
  486. transferenciaController.clear();
  487. } else {
  488. tarjetaController.clear();
  489. }
  490. _calcularCambio(setState);
  491. });
  492. },
  493. disableOtherMethods:
  494. efectivoCompleto || transferenciaCompleto,
  495. onChangedMonto: () => _calcularCambio(setState),
  496. ),
  497. _buildPaymentMethodRow(
  498. setState,
  499. totalPedido,
  500. label: 'Transferencia',
  501. selected: transferenciaSeleccionada,
  502. exactSelected: transferenciaCompleto,
  503. controller: transferenciaController,
  504. sinCambio: true,
  505. onSelected: (value) {
  506. setState(() {
  507. transferenciaSeleccionada = value;
  508. if (!transferenciaSeleccionada) {
  509. transferenciaCompleto = false;
  510. transferenciaController.clear();
  511. }
  512. _calcularCambio(setState);
  513. });
  514. },
  515. onExactSelected: (value) {
  516. setState(() {
  517. transferenciaCompleto = value;
  518. if (transferenciaCompleto) {
  519. transferenciaController.text =
  520. totalPedido.toStringAsFixed(2);
  521. transferenciaSeleccionada = true;
  522. efectivoSeleccionado = false;
  523. tarjetaSeleccionada = false;
  524. efectivoController.clear();
  525. tarjetaController.clear();
  526. } else {
  527. transferenciaController.clear();
  528. }
  529. _calcularCambio(setState);
  530. });
  531. },
  532. disableOtherMethods:
  533. efectivoCompleto || tarjetaCompleto,
  534. onChangedMonto: () => _calcularCambio(setState),
  535. ),
  536. const SizedBox(height: 10),
  537. Align(
  538. alignment: Alignment.centerRight,
  539. child: Column(
  540. crossAxisAlignment: CrossAxisAlignment.end,
  541. children: [
  542. Text(
  543. 'Total del pedido: \$${totalPedido.toStringAsFixed(2)}',
  544. style: const TextStyle(
  545. fontWeight: FontWeight.bold, fontSize: 18),
  546. ),
  547. if (faltante > 0)
  548. Text(
  549. 'Faltante: \$${faltante.toStringAsFixed(2)}',
  550. style: const TextStyle(
  551. color: Colors.red,
  552. fontSize: 18,
  553. fontWeight: FontWeight.bold),
  554. )
  555. else if (cambio > 0)
  556. Text(
  557. 'Cambio: \$${cambio.toStringAsFixed(2)}',
  558. style: const TextStyle(
  559. color: Colors.green,
  560. fontSize: 18,
  561. fontWeight: FontWeight.bold),
  562. ),
  563. ],
  564. ),
  565. ),
  566. ],
  567. ),
  568. ),
  569. ),
  570. actions: [
  571. TextButton(
  572. child: const Text('Cancelar', style: TextStyle(fontSize: 18)),
  573. onPressed: () => Navigator.of(context).pop(false),
  574. style: ButtonStyle(
  575. padding: MaterialStateProperty.all(
  576. EdgeInsets.fromLTRB(30, 20, 30, 20)),
  577. backgroundColor: MaterialStateProperty.all(Colors.red),
  578. foregroundColor:
  579. MaterialStateProperty.all(AppTheme.secondary),
  580. ),
  581. ),
  582. const SizedBox(width: 100),
  583. TextButton(
  584. child: const Text('Guardar', style: TextStyle(fontSize: 18)),
  585. onPressed: totalCompletado
  586. ? () => Navigator.of(context).pop(true)
  587. : null,
  588. style: ButtonStyle(
  589. padding: MaterialStateProperty.all(
  590. EdgeInsets.fromLTRB(30, 20, 30, 20)),
  591. backgroundColor: MaterialStateProperty.all(
  592. totalCompletado ? AppTheme.tertiary : Colors.grey),
  593. foregroundColor:
  594. MaterialStateProperty.all(AppTheme.quaternary),
  595. ),
  596. ),
  597. ],
  598. );
  599. },
  600. );
  601. },
  602. );
  603. if (shouldSave ?? false) {
  604. double cantEfectivo = efectivoSeleccionado
  605. ? double.tryParse(efectivoController.text) ?? 0
  606. : 0;
  607. double cantTarjeta = tarjetaSeleccionada
  608. ? double.tryParse(tarjetaController.text) ?? 0
  609. : 0;
  610. double cantTransferencia = transferenciaSeleccionada
  611. ? double.tryParse(transferenciaController.text) ?? 0
  612. : 0;
  613. pedido.cantEfectivo = cantEfectivo;
  614. pedido.cantTarjeta = cantTarjeta;
  615. pedido.cantTransferencia = cantTransferencia;
  616. List<String> nuevosMetodos = [];
  617. if (cantEfectivo > 0) nuevosMetodos.add('Efectivo');
  618. if (cantTarjeta > 0) nuevosMetodos.add('Tarjeta');
  619. if (cantTransferencia > 0) nuevosMetodos.add('Transferencia');
  620. pedido.tipoPago =
  621. nuevosMetodos.isNotEmpty ? nuevosMetodos.join(',') : 'No Definido';
  622. pedido.sincronizado = null;
  623. await _guardarPedido(pedido, context);
  624. // Recargamos el pedido desde la BD para tener sus datos actualizados
  625. PedidoViewModel viewModel =
  626. Provider.of<PedidoViewModel>(context, listen: false);
  627. Pedido? pedidoActualizado =
  628. await viewModel.fetchPedidoConProductos(pedido.id!);
  629. if (pedidoActualizado != null) {
  630. setState(() {
  631. pedido = pedidoActualizado;
  632. });
  633. }
  634. }
  635. }
  636. Widget _buildPaymentMethodRow(
  637. StateSetter setState,
  638. double totalPedido, {
  639. required String label,
  640. required bool selected,
  641. required bool exactSelected,
  642. required TextEditingController controller,
  643. required Function(bool) onSelected,
  644. required Function(bool) onExactSelected,
  645. required bool disableOtherMethods,
  646. required Function() onChangedMonto,
  647. bool sinCambio = false,
  648. }) {
  649. return Row(
  650. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  651. crossAxisAlignment: CrossAxisAlignment.center,
  652. children: [
  653. Row(
  654. children: [
  655. Checkbox(
  656. activeColor: AppTheme.primary,
  657. value: selected,
  658. onChanged: disableOtherMethods
  659. ? null
  660. : (value) {
  661. onSelected(value ?? false);
  662. },
  663. ),
  664. GestureDetector(
  665. onTap: disableOtherMethods
  666. ? null
  667. : () {
  668. onSelected(!selected);
  669. },
  670. child: Text(
  671. label,
  672. style:
  673. const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
  674. ),
  675. ),
  676. ],
  677. ),
  678. SizedBox(
  679. width: 180,
  680. child: Row(
  681. crossAxisAlignment: CrossAxisAlignment.start,
  682. children: [
  683. Column(
  684. children: [
  685. const Text(
  686. 'Exacto',
  687. style: TextStyle(
  688. fontSize: 18,
  689. fontWeight: FontWeight.bold,
  690. color: Colors.black),
  691. ),
  692. const SizedBox(height: 17),
  693. Checkbox(
  694. activeColor: AppTheme.primary,
  695. value: exactSelected,
  696. onChanged: !disableOtherMethods
  697. ? (value) {
  698. onExactSelected(value ?? false);
  699. if (value == true) {
  700. setState(() {
  701. // nada especial, ya manejamos esto arriba
  702. });
  703. }
  704. }
  705. : null,
  706. ),
  707. ],
  708. ),
  709. const SizedBox(width: 5),
  710. Expanded(
  711. child: AppTextField(
  712. controller: controller,
  713. enabled: selected,
  714. etiqueta: 'Cantidad',
  715. hintText: '0.00',
  716. keyboardType: TextInputType.number,
  717. onChanged: (value) {
  718. if (sinCambio) {
  719. double? input = double.tryParse(value) ?? 0;
  720. if (input > totalPedido) {
  721. controller.text = totalPedido.toStringAsFixed(2);
  722. controller.selection = TextSelection.fromPosition(
  723. TextPosition(offset: controller.text.length),
  724. );
  725. }
  726. }
  727. onChangedMonto();
  728. },
  729. ),
  730. ),
  731. ],
  732. ),
  733. ),
  734. ],
  735. );
  736. }
  737. String _formatDateTime(String? dateTimeString) {
  738. if (dateTimeString == null) return "Sin fecha";
  739. DateTime parsedDate = DateTime.parse(dateTimeString);
  740. var formatter = DateFormat('dd-MM-yyyy HH:mm:ss');
  741. return formatter.format(parsedDate.toLocal());
  742. }
  743. Future<void> _guardarPedido(Pedido pedido, BuildContext buildContext) async {
  744. RepoService<Pedido> repoPedido = RepoService<Pedido>();
  745. RepoService<PedidoProducto> repoPedidoProducto =
  746. RepoService<PedidoProducto>();
  747. RepoService<PedidoProductoTopping> repoPedidoProductoTopping =
  748. RepoService<PedidoProductoTopping>();
  749. try {
  750. if (pedido.id != null && pedido.id! > 0) {
  751. await repoPedido.guardar(pedido);
  752. } else {
  753. pedido.id = await repoPedido.guardarLocal(pedido);
  754. }
  755. List<PedidoProducto> productosExistentes =
  756. await repoPedidoProducto.obtenerPorIdPedido(pedido.id!);
  757. for (var productoExistente in productosExistentes) {
  758. bool sigueExistiendo = pedido.productos.any(
  759. (producto) => producto.idProducto == productoExistente.idProducto);
  760. if (!sigueExistiendo) {
  761. productoExistente.eliminado = DateTime.now().toUtc();
  762. await repoPedidoProducto.guardar(productoExistente);
  763. }
  764. }
  765. for (var producto in pedido.productos) {
  766. PedidoProducto pedidoProducto = PedidoProducto(
  767. idPedido: pedido.id,
  768. idProducto: producto.idProducto,
  769. cantidad: producto.cantidad,
  770. costoUnitario: producto.costoUnitario,
  771. comentario: producto.comentario,
  772. );
  773. PedidoProducto? productoExistente = productosExistentes
  774. .firstWhereOrNull((p) => p.idProducto == pedidoProducto.idProducto);
  775. if (productoExistente != null) {
  776. pedidoProducto.id = productoExistente.id;
  777. await repoPedidoProducto.guardar(pedidoProducto);
  778. } else {
  779. int idPedidoProducto =
  780. await repoPedidoProducto.guardarLocal(pedidoProducto);
  781. for (var topping in producto.toppings) {
  782. PedidoProductoTopping pedidoProductoTopping = PedidoProductoTopping(
  783. idPedidoProducto: idPedidoProducto,
  784. idTopping: topping.idTopping,
  785. idCategoria: topping.idCategoria,
  786. );
  787. await repoPedidoProductoTopping.guardarLocal(pedidoProductoTopping);
  788. }
  789. }
  790. }
  791. ScaffoldMessenger.of(buildContext).showSnackBar(
  792. const SnackBar(content: Text("Pedido actualizado correctamente.")),
  793. );
  794. } catch (e) {
  795. print("Error al guardar el pedido: $e");
  796. ScaffoldMessenger.of(buildContext).showSnackBar(
  797. const SnackBar(content: Text("Error al actualizar el pedido.")),
  798. );
  799. }
  800. }
  801. }