pedido_ticket.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import 'dart:typed_data';
  2. import 'package:flutter/material.dart';
  3. import 'package:intl/intl.dart';
  4. import 'package:pdf/pdf.dart';
  5. import 'package:pdf/widgets.dart' as pw;
  6. import 'package:provider/provider.dart';
  7. import '../../models/models.dart';
  8. import 'package:printing/printing.dart';
  9. import 'package:flutter/services.dart' show rootBundle;
  10. import '../../viewmodels/viewmodels.dart';
  11. const int _gamepadCode = 0xf11b;
  12. Future<void> imprimirTicketsJuntos(BuildContext context, Pedido pedido) async {
  13. bool ticketCocinaActivo =
  14. await Provider.of<VariableViewModel>(context, listen: false)
  15. .isVariableActive('ticket_cocina');
  16. final pdf = pw.Document();
  17. final image = pw.MemoryImage(
  18. (await rootBundle.load('assets/logo.png')).buffer.asUint8List(),
  19. );
  20. final igImage = pw.MemoryImage(
  21. (await rootBundle.load('assets/igLogo.png')).buffer.asUint8List(),
  22. );
  23. final phoneImage = pw.MemoryImage(
  24. (await rootBundle.load('assets/phoneImage.png')).buffer.asUint8List(),
  25. );
  26. final webImage = pw.MemoryImage(
  27. (await rootBundle.load('assets/web.png')).buffer.asUint8List(),
  28. );
  29. pdf.addPage(
  30. generarPaginaPrimerTicket(pedido, image, igImage, phoneImage, webImage),
  31. );
  32. if (ticketCocinaActivo) {
  33. pdf.addPage(
  34. generarPaginaSegundoTicket(pedido),
  35. );
  36. }
  37. await printPdf(Uint8List.fromList(await pdf.save()));
  38. }
  39. pw.Page generarPaginaPrimerTicket(
  40. Pedido pedido,
  41. pw.MemoryImage image,
  42. pw.MemoryImage igImage,
  43. pw.MemoryImage phoneImage,
  44. pw.MemoryImage webImage) {
  45. final numberFormat = NumberFormat('#,##0.00', 'es_MX');
  46. double totalSinDescuento = 0;
  47. double totalConDescuento = 0;
  48. double descuento = pedido.descuento?.toDouble() ?? 0.0;
  49. double precioDescuento = 0;
  50. final productList = pedido.productos
  51. .map(
  52. (producto) {
  53. final productPrice = producto.producto?.precio ?? 0.0;
  54. final productTotal = productPrice * (producto.cantidad ?? 1);
  55. totalSinDescuento += productTotal;
  56. final toppingsList = producto.toppings.where((topping) {
  57. final toppingPrice = topping.topping?.precio ?? 0.0;
  58. return toppingPrice > 0;
  59. }).map((topping) {
  60. final toppingPrice = topping.topping?.precio ?? 0.0;
  61. totalSinDescuento += toppingPrice * (producto.cantidad ?? 1);
  62. return pw.Row(
  63. children: [
  64. pw.Text(
  65. '- ${topping.topping?.nombre ?? "Topping no especificado"}',
  66. style: const pw.TextStyle(fontSize: 7)),
  67. pw.Spacer(),
  68. pw.Text('\$${numberFormat.format(toppingPrice)}',
  69. style: const pw.TextStyle(fontSize: 7)),
  70. ],
  71. );
  72. }).toList();
  73. return [
  74. pw.Row(
  75. mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
  76. children: [
  77. pw.Expanded(
  78. flex: 2,
  79. child: pw.Text(
  80. producto.producto?.nombre ?? "Producto no especificado",
  81. style: const pw.TextStyle(fontSize: 7)),
  82. ),
  83. pw.Expanded(
  84. flex: 1,
  85. child: pw.Text('x${producto.cantidad}',
  86. style: const pw.TextStyle(fontSize: 7)),
  87. ),
  88. pw.Padding(
  89. padding: pw.EdgeInsets.only(right: 2),
  90. child: pw.Expanded(
  91. flex: 1,
  92. child: pw.Text('\$${numberFormat.format(productPrice)}',
  93. style: const pw.TextStyle(fontSize: 8)),
  94. ),
  95. )
  96. ],
  97. ),
  98. ...toppingsList,
  99. ];
  100. },
  101. )
  102. .expand((e) => e)
  103. .toList();
  104. precioDescuento = totalSinDescuento * (descuento / 100);
  105. totalConDescuento = totalSinDescuento - precioDescuento;
  106. return pw.Page(
  107. pageFormat: PdfPageFormat.roll57,
  108. build: (pw.Context context) {
  109. return pw.Column(
  110. crossAxisAlignment: pw.CrossAxisAlignment.center,
  111. children: [
  112. pw.Padding(
  113. padding: const pw.EdgeInsets.only(right: 15),
  114. child:
  115. pw.Center(child: pw.Image(image, width: 50, height: 50))),
  116. pw.SizedBox(height: 10),
  117. pw.Padding(
  118. padding: const pw.EdgeInsets.only(right: 15),
  119. child: pw.Column(children: [
  120. pw.Padding(
  121. padding: pw.EdgeInsets.only(left: 10),
  122. child: pw.Text('Turquessa Coffee',
  123. style: pw.TextStyle(
  124. fontSize: 12, fontWeight: pw.FontWeight.bold))),
  125. pw.SizedBox(height: 10),
  126. pw.Row(children: []),
  127. pw.Text('Fecha: ${_formatDateTime(pedido.peticion)}',
  128. style: const pw.TextStyle(fontSize: 9)),
  129. pw.Text('Hermosillo',
  130. style: const pw.TextStyle(fontSize: 9)),
  131. pw.SizedBox(height: 5),
  132. pw.Row(children: [
  133. pw.Image(igImage, width: 8, height: 8),
  134. pw.SizedBox(width: 20),
  135. pw.Text('turquessacoffee',
  136. style: pw.TextStyle(fontSize: 9)),
  137. ]),
  138. pw.Row(children: [
  139. pw.Image(phoneImage, width: 8, height: 8),
  140. pw.SizedBox(width: 25),
  141. pw.Text('662-466-6626',
  142. textAlign: pw.TextAlign.center,
  143. style: pw.TextStyle(fontSize: 9)),
  144. ]),
  145. pw.Row(
  146. mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
  147. children: [
  148. pw.Image(webImage, width: 8, height: 8),
  149. pw.SizedBox(width: 5),
  150. pw.Text('https://turquessacoffee.com/',
  151. style: pw.TextStyle(fontSize: 9))
  152. ]),
  153. ])),
  154. pw.SizedBox(height: 10),
  155. pw.Text('Pedido: ${pedido.folio}',
  156. style: pw.TextStyle(
  157. fontWeight: pw.FontWeight.bold, fontSize: 10)),
  158. pw.SizedBox(height: 10),
  159. pw.Padding(
  160. padding: const pw.EdgeInsets.only(right: 20),
  161. child: pw.Column(children: productList)),
  162. pw.Divider(),
  163. pw.Row(
  164. mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
  165. children: [
  166. pw.Text('Subtotal:',
  167. style: pw.TextStyle(
  168. fontWeight: pw.FontWeight.bold, fontSize: 9)),
  169. pw.Padding(
  170. padding: const pw.EdgeInsets.only(right: 30),
  171. child: pw.Text(
  172. '\$${numberFormat.format(totalSinDescuento)}',
  173. style: pw.TextStyle(
  174. fontWeight: pw.FontWeight.bold, fontSize: 9)),
  175. ),
  176. ],
  177. ),
  178. if (descuento > 0) ...[
  179. pw.Row(
  180. mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
  181. children: [
  182. pw.Text('Descuento:',
  183. style: pw.TextStyle(
  184. fontWeight: pw.FontWeight.bold, fontSize: 9)),
  185. pw.Padding(
  186. padding: const pw.EdgeInsets.only(right: 30),
  187. child: pw.Text(
  188. '-\$${numberFormat.format(precioDescuento)}',
  189. style: pw.TextStyle(
  190. fontWeight: pw.FontWeight.bold, fontSize: 9)),
  191. ),
  192. ],
  193. ),
  194. ],
  195. pw.Row(
  196. mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
  197. children: [
  198. pw.Text('Total:',
  199. style: pw.TextStyle(
  200. fontWeight: pw.FontWeight.bold, fontSize: 9)),
  201. pw.Padding(
  202. padding: const pw.EdgeInsets.only(right: 30),
  203. child: pw.Text(
  204. '\$${numberFormat.format(totalConDescuento)}',
  205. style: pw.TextStyle(
  206. fontWeight: pw.FontWeight.bold, fontSize: 9)),
  207. ),
  208. ],
  209. ),
  210. pw.SizedBox(height: 5),
  211. pw.Padding(
  212. padding: const pw.EdgeInsets.only(right: 15),
  213. child: pw.Text('¡GRACIAS POR SU COMPRA!',
  214. style: pw.TextStyle(
  215. fontSize: 8, fontWeight: pw.FontWeight.bold))),
  216. pw.Divider(),
  217. pw.SizedBox(height: 20),
  218. pw.Text('.', style: pw.TextStyle(fontSize: 1)),
  219. ]);
  220. });
  221. }
  222. pw.Page generarPaginaSegundoTicket(Pedido pedido) {
  223. return pw.Page(
  224. pageFormat: PdfPageFormat.roll57,
  225. build: (pw.Context context) {
  226. List<pw.Widget> content = [
  227. pw.Padding(
  228. padding: const pw.EdgeInsets.only(right: 15),
  229. child: pw.Text('Fecha: ${_formatDateTime(pedido.peticion)}',
  230. style: pw.TextStyle(
  231. fontSize: 9, fontWeight: pw.FontWeight.bold))),
  232. pw.SizedBox(height: 5),
  233. pw.Text('Pedido: ${pedido.folio}',
  234. style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 9)),
  235. pw.SizedBox(height: 10),
  236. pw.Text('Cliente: ${pedido.nombreCliente}',
  237. style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 9)),
  238. pw.SizedBox(height: 10),
  239. ];
  240. content.addAll(pedido.productos
  241. .map((producto) {
  242. final productPrice = producto.producto?.precio ?? 0.0;
  243. final productTotal = productPrice * (producto.cantidad ?? 1);
  244. final toppingsList = producto.toppings.map((topping) {
  245. return pw.Row(
  246. children: [
  247. pw.Text(
  248. '-${topping.topping?.nombre ?? "Topping no especificado"}',
  249. style: const pw.TextStyle(fontSize: 7)),
  250. ],
  251. );
  252. }).toList();
  253. return [
  254. pw.Column(children: [
  255. pw.Row(
  256. mainAxisAlignment: pw.MainAxisAlignment.start,
  257. children: [
  258. pw.Expanded(
  259. flex: 3,
  260. child: pw.Text(
  261. producto.producto?.nombre ??
  262. "Producto no especificado",
  263. style: const pw.TextStyle(fontSize: 9)),
  264. ),
  265. pw.Expanded(
  266. flex: 1,
  267. child: pw.Text('x${producto.cantidad}',
  268. style: const pw.TextStyle(fontSize: 9)),
  269. ),
  270. ],
  271. ),
  272. if (producto.comentario!.isNotEmpty)
  273. pw.Row(
  274. mainAxisAlignment: pw.MainAxisAlignment.start,
  275. children: [
  276. pw.Text('- ${producto.comentario}' ?? "",
  277. style: const pw.TextStyle(fontSize: 8)),
  278. ],
  279. ),
  280. pw.SizedBox(height: 5),
  281. ]),
  282. ...toppingsList,
  283. ];
  284. })
  285. .expand((e) => e)
  286. .toList());
  287. if (pedido.comentarios != null && pedido.comentarios!.isNotEmpty) {
  288. content.add(pw.SizedBox(height: 10));
  289. content.add(pw.Text('Comentarios:',
  290. style:
  291. pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 9)));
  292. content.add(pw.Padding(
  293. padding: const pw.EdgeInsets.only(right: 15),
  294. child: pw.Text(pedido.comentarios!,
  295. style: const pw.TextStyle(fontSize: 9)),
  296. ));
  297. content.add(pw.Text('.', style: pw.TextStyle(fontSize: 1)));
  298. content.add(pw.Text('.', style: pw.TextStyle(fontSize: 1)));
  299. }
  300. content.add(pw.SizedBox(height: 20));
  301. content.add(pw.Text('.', style: pw.TextStyle(fontSize: 1)));
  302. return pw.Column(
  303. crossAxisAlignment: pw.CrossAxisAlignment.center,
  304. children: content);
  305. });
  306. }
  307. Future<void> printPdf(Uint8List pdfBytes) async {
  308. await Printing.layoutPdf(
  309. onLayout: (PdfPageFormat format) => pdfBytes,
  310. );
  311. }
  312. String _formatDateTime(String? dateTimeString) {
  313. if (dateTimeString == null) return "Sin fecha";
  314. DateTime parsedDate = DateTime.parse(dateTimeString);
  315. var formatter = DateFormat('dd-MM-yyyy HH:mm:ss');
  316. return formatter.format(parsedDate.toLocal());
  317. }