venta_screen.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. import 'package:flutter/material.dart';
  2. import 'package:intl/intl.dart';
  3. import 'package:omni_datetime_picker/omni_datetime_picker.dart';
  4. import 'package:provider/provider.dart';
  5. import 'package:turquessa_app/views/venta/venta_csv.dart';
  6. import '../../widgets/widgets_components.dart';
  7. import '../../models/models.dart';
  8. import '../../viewmodels/viewmodels.dart';
  9. import 'venta_ticket.dart';
  10. import '../../widgets/widgets.dart';
  11. import '../../themes/themes.dart';
  12. class VentaScreen extends StatefulWidget {
  13. @override
  14. _VentaScreenState createState() => _VentaScreenState();
  15. }
  16. class _VentaScreenState extends State<VentaScreen> {
  17. DateTime? fechaInicialSeleccionada;
  18. DateTime? fechaFinalSeleccionada;
  19. List<Pedido> pedidosNoCancelados = [];
  20. List<Pedido> pedidosCancelados = [];
  21. List<Propinas> listaPropinas = [];
  22. final _busqueda = TextEditingController(text: '');
  23. double totalDelDia = 0.0;
  24. double totalPropinas = 0.0;
  25. double totalCancelados = 0.0;
  26. double totalEfectivoDelDia = 0.0;
  27. double totalTarjetaDelDia = 0.0;
  28. double totalTransferenciaDelDia = 0.0;
  29. double cambio = 0.0;
  30. double totalSinCambio = 0.0;
  31. String formatCurrency(double amount) {
  32. final format = NumberFormat("#,##0.00", "es_MX");
  33. return format.format(amount);
  34. }
  35. void clearSearchAndReset() {
  36. setState(() {
  37. _busqueda.clear();
  38. fechaInicialSeleccionada = null;
  39. fechaFinalSeleccionada = null;
  40. });
  41. }
  42. @override
  43. Widget build(BuildContext context) {
  44. return Scaffold(
  45. appBar: AppBar(
  46. title: Text(
  47. "Resumen de Pedidos por Periodo",
  48. style: TextStyle(color: AppTheme.secondary),
  49. ),
  50. actions: <Widget>[
  51. IconButton(
  52. icon: const Icon(Icons.save_alt),
  53. onPressed: () {
  54. exportCSV(fechaInicialSeleccionada, fechaFinalSeleccionada);
  55. },
  56. tooltip: 'Exportar a CSV',
  57. ),
  58. ],
  59. iconTheme: IconThemeData(color: AppTheme.secondary)),
  60. body: Padding(
  61. padding: const EdgeInsets.all(16.0),
  62. child: Column(
  63. children: [
  64. Row(
  65. children: [
  66. Expanded(
  67. flex: 3,
  68. child: Padding(
  69. padding: const EdgeInsets.symmetric(horizontal: 0.0),
  70. child: tarjeta(
  71. ListTile(
  72. title: Text(
  73. "Fecha Inicial",
  74. style: TextStyle(
  75. color: AppTheme.quaternary,
  76. fontWeight: FontWeight.bold),
  77. ),
  78. subtitle: Text(
  79. fechaInicialSeleccionada == null
  80. ? ""
  81. : DateFormat("dd/MM/yyyy")
  82. .format(fechaInicialSeleccionada!),
  83. style: TextStyle(
  84. color: AppTheme.quaternary,
  85. fontWeight: FontWeight.bold),
  86. ),
  87. trailing: Icon(Icons.calendar_month,
  88. color: AppTheme.quaternary),
  89. onTap: () async {
  90. DateTime? d = await showDatetimePicker(
  91. context, fechaInicialSeleccionada,
  92. inicia: fechaInicialSeleccionada,
  93. tipo: OmniDateTimePickerType.date,
  94. solofecha: true);
  95. if (d == null) return;
  96. setState(() {
  97. fechaInicialSeleccionada = d;
  98. });
  99. cargarPedidos(fechaInicialSeleccionada,
  100. fechaFinalSeleccionada);
  101. }),
  102. color: AppTheme.tertiary,
  103. ),
  104. )),
  105. const SizedBox(
  106. width: 10,
  107. ),
  108. Expanded(
  109. flex: 3,
  110. child: Padding(
  111. padding: const EdgeInsets.symmetric(horizontal: 0.0),
  112. child: tarjeta(
  113. ListTile(
  114. title: Text(
  115. "Fecha Final",
  116. style: TextStyle(
  117. color: AppTheme.quaternary,
  118. fontWeight: FontWeight.bold),
  119. ),
  120. subtitle: Text(
  121. fechaFinalSeleccionada == null
  122. ? ""
  123. : DateFormat("dd/MM/yyyy")
  124. .format(fechaFinalSeleccionada!),
  125. style: TextStyle(
  126. color: AppTheme.quaternary,
  127. fontWeight: FontWeight.bold),
  128. ),
  129. trailing: Icon(Icons.calendar_month,
  130. color: AppTheme.quaternary),
  131. onTap: () async {
  132. DateTime? d = await showDatetimePicker(
  133. context, fechaFinalSeleccionada,
  134. inicia: fechaFinalSeleccionada,
  135. tipo: OmniDateTimePickerType.date,
  136. solofecha: true);
  137. if (d == null) return;
  138. setState(() {
  139. fechaFinalSeleccionada = d;
  140. });
  141. cargarPedidos(fechaInicialSeleccionada,
  142. fechaFinalSeleccionada);
  143. }),
  144. color: AppTheme.tertiary,
  145. ),
  146. )),
  147. const SizedBox(
  148. width: 500,
  149. ),
  150. Expanded(
  151. flex: 2,
  152. child: Padding(
  153. padding: const EdgeInsets.only(top: 0),
  154. child: ElevatedButton(
  155. onPressed: clearSearchAndReset,
  156. style: ElevatedButton.styleFrom(
  157. shape: RoundedRectangleBorder(
  158. borderRadius: BorderRadius.circular(20.0),
  159. ),
  160. backgroundColor: AppTheme.tertiary,
  161. padding: const EdgeInsets.symmetric(vertical: 25),
  162. ),
  163. child: Text('Limpiar',
  164. style: TextStyle(
  165. color: AppTheme.quaternary, fontSize: 18)),
  166. ),
  167. ),
  168. ),
  169. ],
  170. ),
  171. Expanded(
  172. child: tarjeta(
  173. Padding(
  174. padding: const EdgeInsets.all(8.0),
  175. child: ListView.builder(
  176. itemCount:
  177. pedidosNoCancelados.length + pedidosCancelados.length,
  178. itemBuilder: (context, index) {
  179. if (index < pedidosNoCancelados.length) {
  180. final pedido = pedidosNoCancelados[index];
  181. return Row(
  182. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  183. children: [
  184. Text(
  185. "Folio: ${pedido.folio}",
  186. style: TextStyle(
  187. fontSize: 20, fontWeight: FontWeight.w500),
  188. ),
  189. Text(
  190. "Fecha/Hora: ${_formatDateTime(pedido.peticion)}",
  191. style: TextStyle(
  192. fontSize: 20, fontWeight: FontWeight.w500),
  193. ),
  194. Text(
  195. "Propina: \$${formatCurrency(_propinaPedido(pedido.id) ?? 0)}",
  196. style: TextStyle(
  197. fontSize: 20, fontWeight: FontWeight.w500),
  198. ),
  199. Text(
  200. "Total: \$${formatCurrency(pedido.totalPedido ?? 0)}",
  201. style: TextStyle(
  202. fontSize: 20, fontWeight: FontWeight.w500)),
  203. ],
  204. );
  205. } else {
  206. final pedido =
  207. pedidosCancelados[index - pedidosNoCancelados.length];
  208. return Row(
  209. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  210. children: [
  211. Text(
  212. "Folio: ${pedido.folio} (Cancelado)",
  213. style: TextStyle(
  214. fontSize: 20,
  215. fontWeight: FontWeight.w500,
  216. color: Colors.red),
  217. ),
  218. Text(
  219. "Fecha/Hora: ${_formatDateTime(pedido.peticion)}",
  220. style: TextStyle(
  221. fontSize: 20, fontWeight: FontWeight.w500),
  222. ),
  223. Text(
  224. "Propina: \$${formatCurrency(_propinaPedido(pedido.id) ?? 0)}",
  225. style: TextStyle(
  226. fontSize: 20, fontWeight: FontWeight.w500),
  227. ),
  228. Text(
  229. "Total: \$${formatCurrency(pedido.totalPedido ?? 0)}",
  230. style: TextStyle(
  231. fontSize: 20,
  232. fontWeight: FontWeight.w500,
  233. color: Colors.red)),
  234. ],
  235. );
  236. }
  237. },
  238. ),
  239. ),
  240. )),
  241. Row(
  242. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  243. children: [
  244. ElevatedButton.icon(
  245. icon: Icon(
  246. Icons.receipt_long_outlined,
  247. color: AppTheme.quaternary,
  248. size: 30,
  249. ),
  250. onPressed: pedidosNoCancelados.isNotEmpty ||
  251. pedidosCancelados.isNotEmpty
  252. ? () => imprimirResumenPedidos(
  253. pedidosNoCancelados + pedidosCancelados)
  254. : null,
  255. label: Text(
  256. "Imprimir Resumen",
  257. style: TextStyle(color: AppTheme.quaternary, fontSize: 18),
  258. ),
  259. style: ButtonStyle(
  260. backgroundColor:
  261. MaterialStatePropertyAll(AppTheme.tertiary),
  262. padding: MaterialStatePropertyAll(
  263. EdgeInsets.fromLTRB(30, 20, 30, 20))),
  264. ),
  265. Column(
  266. crossAxisAlignment: CrossAxisAlignment.end,
  267. children: [
  268. Row(
  269. children: [
  270. Padding(
  271. padding: const EdgeInsets.all(16.0),
  272. child: Text(
  273. "Total Propina: \$${formatCurrency(totalPropinas)}",
  274. style: TextStyle(
  275. fontSize: 20, fontWeight: FontWeight.bold),
  276. ),
  277. ),
  278. Padding(
  279. padding: const EdgeInsets.all(16.0),
  280. child: Text(
  281. "Total: \$${formatCurrency(totalDelDia)}",
  282. style: TextStyle(
  283. fontSize: 20, fontWeight: FontWeight.bold),
  284. ),
  285. ),
  286. ],
  287. ),
  288. Row(
  289. children: [
  290. if (totalTarjetaDelDia > 0)
  291. Padding(
  292. padding: const EdgeInsets.all(16.0),
  293. child: Text(
  294. "Total en Tarjeta: \$${formatCurrency(totalTarjetaDelDia)}",
  295. style: TextStyle(
  296. fontSize: 20, fontWeight: FontWeight.bold),
  297. ),
  298. ),
  299. if (totalTransferenciaDelDia > 0)
  300. Padding(
  301. padding: const EdgeInsets.all(16.0),
  302. child: Text(
  303. "Total en Transferencia: \$${formatCurrency(totalTransferenciaDelDia)}",
  304. style: TextStyle(
  305. fontSize: 20, fontWeight: FontWeight.bold),
  306. ),
  307. ),
  308. if (totalEfectivoDelDia > 0)
  309. Padding(
  310. padding: const EdgeInsets.all(16.0),
  311. child: Text(
  312. "Total en Efectivo: \$${formatCurrency(totalEfectivoDelDia)}",
  313. style: TextStyle(
  314. fontSize: 20, fontWeight: FontWeight.bold),
  315. ),
  316. ),
  317. if (cambio > 0)
  318. Padding(
  319. padding: const EdgeInsets.all(16.0),
  320. child: Text(
  321. "Cambio Entregado: \$${formatCurrency(cambio)}",
  322. style: TextStyle(
  323. fontSize: 20, fontWeight: FontWeight.bold),
  324. ),
  325. ),
  326. ],
  327. ),
  328. if (totalCancelados > 0)
  329. Padding(
  330. padding: const EdgeInsets.all(16.0),
  331. child: Text(
  332. "Total cancelados: \$${formatCurrency(totalCancelados)}",
  333. style: TextStyle(
  334. fontSize: 20,
  335. fontWeight: FontWeight.bold,
  336. color: Colors.red),
  337. ),
  338. ),
  339. ],
  340. ),
  341. ],
  342. )
  343. ],
  344. ),
  345. ),
  346. );
  347. }
  348. void cargarPedidos(DateTime? fechaInicial, DateTime? fechaFinal) async {
  349. if (fechaInicial != null && fechaFinal != null) {
  350. // Convertir el inicio y fin del día local a UTC
  351. final inicioDelDia =
  352. DateTime(fechaInicial.year, fechaInicial.month, fechaInicial.day)
  353. .toUtc(); // Convierte la fecha local de inicio del día a UTC
  354. final finDelDia = DateTime(
  355. fechaFinal.year, fechaFinal.month, fechaFinal.day, 23, 59, 59)
  356. .toUtc(); // Convierte la fecha local de fin del día a UTC
  357. print('Buscando pedidos desde: $inicioDelDia hasta: $finDelDia (en UTC)');
  358. // Realizar la búsqueda en UTC
  359. final pedidos = await Provider.of<PedidoViewModel>(context, listen: false)
  360. .buscarPorFecha(inicioDelDia, finDelDia);
  361. print('Pedidos obtenidos: ${pedidos.length}');
  362. pedidos.forEach((pedido) => print(
  363. 'Pedido: ${pedido.folio}, Total: ${pedido.totalPedido}, Estatus: ${pedido.estatus}, Propinas: ${pedido.propinas.length}'));
  364. final pedidosNoCancelados =
  365. pedidos.where((p) => p.estatus != "CANCELADO").toList();
  366. final pedidosCancelados =
  367. pedidos.where((p) => p.estatus == "CANCELADO").toList();
  368. totalDelDia = 0.0;
  369. totalPropinas = 0.0;
  370. totalCancelados = 0.0;
  371. totalEfectivoDelDia = 0.0;
  372. totalTarjetaDelDia = 0.0;
  373. totalTransferenciaDelDia = 0.0;
  374. cambio = 0.0;
  375. totalSinCambio = 0.0;
  376. for (var pedido in pedidosNoCancelados) {
  377. totalDelDia += pedido.totalPedido ?? 0.0;
  378. totalEfectivoDelDia += pedido.cantEfectivo ?? 0.0;
  379. totalTarjetaDelDia += pedido.cantTarjeta ?? 0.0;
  380. totalTransferenciaDelDia += pedido.cantTransferencia ?? 0.0;
  381. totalSinCambio =
  382. totalEfectivoDelDia + totalTarjetaDelDia + totalTransferenciaDelDia;
  383. cambio = totalSinCambio - totalDelDia;
  384. print("Propinas: ${pedido.propinas.length}");
  385. // if (pedido.propinas.length > 0) {
  386. // for (var propina in pedido.propinas) {
  387. // totalPropinas += propina.cantidad ?? 0.0;
  388. // }
  389. // }
  390. final propinas =
  391. await Provider.of<PropinaViewModel>(context, listen: false)
  392. .obtenerPropinasPorPedido(pedido.id);
  393. listaPropinas = propinas;
  394. for (var propina in propinas) {
  395. totalPropinas += propina.cantidad ?? 0.0;
  396. }
  397. print("Total propinas: ${totalPropinas}");
  398. }
  399. totalCancelados = pedidosCancelados.fold(
  400. 0.0, (sum, current) => sum + (current.totalPedido ?? 0.0));
  401. setState(() {
  402. this.pedidosNoCancelados = pedidosNoCancelados;
  403. this.pedidosCancelados = pedidosCancelados;
  404. });
  405. }
  406. }
  407. void exportCSV(DateTime? fechaInicial, DateTime? fechaFinal) async {
  408. final pedidosViewModel =
  409. Provider.of<PedidoViewModel>(context, listen: false);
  410. List<Pedido> pedidosConProductos = [];
  411. List<Propinas> propinas = [];
  412. List<Propinas> altPropinas = [];
  413. for (Pedido pedido in pedidosViewModel.pedidos) {
  414. Pedido? pedidoConProductos =
  415. await pedidosViewModel.fetchPedidoConProductos(pedido.id);
  416. if (pedidoConProductos != null) {
  417. pedidosConProductos.add(pedidoConProductos);
  418. }
  419. }
  420. if (pedidosConProductos.isNotEmpty) {
  421. String fileName = 'Ventas_Turquessa_POS';
  422. if (fechaInicial != null && fechaFinal != null) {
  423. String startDateStr = DateFormat('dd-MM-yyyy').format(fechaInicial!);
  424. String endDateStr = DateFormat('dd-MM-yyyy').format(fechaFinal!);
  425. fileName += '_${startDateStr}_al_${endDateStr}';
  426. }
  427. fileName += '.csv';
  428. for (var pedido in pedidosConProductos) {
  429. altPropinas =
  430. await Provider.of<PropinaViewModel>(context, listen: false)
  431. .obtenerPropinasPorPedido(pedido.id);
  432. }
  433. for (var alt in altPropinas) {
  434. propinas.add(alt);
  435. }
  436. await exportarVentasACSV(pedidosConProductos, propinas, fileName);
  437. ScaffoldMessenger.of(context).showSnackBar(SnackBar(
  438. content: Text('Archivo CSV descargado! Archivo: $fileName')));
  439. } else {
  440. ScaffoldMessenger.of(context).showSnackBar(
  441. SnackBar(content: Text('No hay pedidos para exportar.')));
  442. }
  443. }
  444. Future<void> imprimirResumenPedidos(List<Pedido> pedidos) async {
  445. if (fechaInicialSeleccionada == null) {
  446. print("No se ha seleccionado una fecha inicial.");
  447. return;
  448. }
  449. if (fechaFinalSeleccionada == null) {
  450. print("No se ha seleccionado una fecha final.");
  451. return;
  452. }
  453. await VentaTicket.imprimirResumenPedidos(
  454. pedidos, fechaInicialSeleccionada!);
  455. }
  456. String _formatDateTime(String? dateTimeString) {
  457. if (dateTimeString == null) return "Sin fecha";
  458. DateTime parsedDate = DateTime.parse(dateTimeString);
  459. var formatter = DateFormat('dd-MM-yyyy HH:mm:ss');
  460. return formatter.format(parsedDate.toLocal());
  461. }
  462. double? _propinaPedido(int? idPedido) {
  463. double? propina = 0.0;
  464. for (var p in listaPropinas) {
  465. if (p.idPedido == idPedido) {
  466. propina = p.cantidad;
  467. }
  468. }
  469. return propina;
  470. }
  471. }