pedido_detalle_screen.dart 35 KB

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