2 # -*- coding: utf-8 -*-
3 __licence__ = "GNU/GPLv3"
4 __author__ = "Marcelo Zunino (InfoPrimo SL) 2015-2017"
6 # from time import strftime
13 import inspect as insp
15 from datetime import datetime
16 from typing import Type
17 from decimal import Decimal
19 from spazospy.ticket import IPCabezal, IPLinea
20 from chk_json import SqliteTckJson
21 from util import config
22 from parser import parse_lines
26 # fantes = open('antes.json', "w") ; fantes.close()
27 # fahora = open('ahora.json', "w") ; fahora.close()
33 logfile = './log/opentck3.log'
34 os.path.isdir('./log') or os.mkdir('./log')
35 os.path.isfile(logfile) or os.system("touch %s" % logfile)
37 logging.basicConfig(format='%(asctime)s %(message)s', filename=logfile, level=logging.INFO)
41 # from inspect import currentframe, getframeinfo
45 msg = msg or "\n\tAlgo salio mal... FIN\n"
46 sys.stdout.write(msg, )
49 def rell(rawcode=None, largo=6):
53 return (largo - len(str(rawcode))) * '0' + str(rawcode)
57 output = subprocess.check_output(command, shell=True)
61 def get_date(info_file):
62 """ :param info_file: archivo tckAAAAMMDD.tck
63 :return: texto: una fecha
65 Parsea la primera línea del informe creado.
66 si bien el nombre del archivo representa a un fecha, su contenido puede corresponder a una fecha diferente.
68 ejemplo de primera línea de informe (siempre será un cabezal de ticket)
69 C#8#1#87#8#20151231080308#F#0#0.00#20#8
73 :param info_file: archivo tckAAAAMMDD.tck
74 :return: texto: una fecha
80 head = run("head -1 %s" % info_file)
81 head = head.strip('\n')
83 for i in range(len(head)):
86 if count_sep >= 5 and centinel < 9:
90 date = date.strip('#')
98 class Excepcion(object):
100 stdout_err = 1 # `stdout_err = 1` mostrará errores en stdout
101 verbose = 1 # `verbose = 1` mostrará detalles del error
102 debug_log = 0 # `debug = 1` escribe detalles en el log
103 debug_vivo = 1 # `debug_vivo = 1` abre consola de debug en el error
105 def __init__(self, mensaje, frame=None, id_ticket=None):
106 # type: (object, object, object) -> object
107 self.msg = mensaje or " algo salió mal... "
110 self.id_ticket = id_ticket
113 """ logging / notificación a stdout """
117 id_ticket = self.id_ticket
118 if self.stdout_err and msg:
119 print("\t %s" % (msg,))
120 if self.verbose and frame and id_ticket:
121 msgv = "aaaammdd cja tck: %s" % (id_ticket,)
122 msgv += " en: %s lín: %s" % (frame.filename, frame.lineno)
123 print("\t %s" % (msgv,))
126 msg += " fin ejecución..."
134 excepcion = Excepcion # type: Type[Excepcion]
137 class OpenTck(object):
143 def __init__(self, salidapazos):
145 if not os.path.isfile(salidapazos):
146 msg = " %s El informe no exixte o no es accesible." % (ns,)
152 self.salidapazos = salidapazos
156 ::PROCESA UN UNICO INFORME::
157 ============================
158 retorna: diccionario:
159 { cabezal_tck0: linea_tck_pazos0, cabezal_tck1: linea_tck_pazos1, ... }
162 Dada la línea de cabezal: C#1#2#350#2#20151121105326#F#4#183.40#20#13
163 Cabezal_Tck1 = ('1', '2', '350', '2', '20151121105326', 'F', '4', '183.40', '20', '13')
165 Dada la línea de detalle: L#4#53#120435#TICKET DE VENTA#0
166 LinTckN_2 = ('4', '53', '120435', 'TICKET DE VENTA', '0')
168 En ambos casos se omite el identificador de línea, "C" o "L"
171 salidapazos = self.salidapazos or None
173 msgout("\n\n\t\t\t\t\t Verificar ingreso ... %s" % ('\n\n',))
179 with open(salidapazos, 'r') as f:
180 reader = csv.reader(f, delimiter='#')
182 info_line += 1 # sólo para sber dónde se corta, si es que se corta.
185 msg = ' %s La línea está vacía.' % (ns,)
187 sys.stdout.write("\n\t%s\n\n" % (msg,))
190 key = tuple(row[1:]) # llave = línea de cabezal sin el primer caracter, o sea sin la "C"
193 informe[key].append(tuple(row[1:])) # valores = líneas de detalle de ticket
196 msgout("\n\n\t\t\t\t\tEl infome salidapazos puede contener erores\n\t\t\t\t\t")
198 "\n\t\t\t\t\tNo se generaron los informes para \'findía\' Ver log.. %s\n\n" % (logfile,))
200 " %s [ Error ] : Verificar el contenido de %s at line %s" % (ns, salidapazos, info_line))
203 msg = " %s [ Error ] : Ocurrió un error al leer informe. " % (ns,)
204 msg += "Verificar %s. No se generó el archivo .json" % (salidapazos,)
209 return informe # { tupla:: cabezal_tck0: lista de tuplas:: linea_tck_pazos0, ... }
211 def do_cabezales(self, cabezal, lineas_cabezal):
217 Procesa los tickets (llama rutina que crea las lineas)
218 tickets = { cabezal_Ticket0: [ lineaTicket0_0, lineaTicket0_1, lineaTicket0_2, lineaTicket0_N ],
219 cabezal_Ticket1: [ lineaTicket1_0, ... ],
220 cabezal_TicketN: [ lineaTicketN_0, ....]
222 cabezal_Ticket :: lista de tuplas
225 Recibe los tickets del informe y parsea aquellos definidos en la configuración como `cabezales de interés`.
226 1. Instancia la clase `IPCabezal` con los datos de cabezal disponibles.
227 Algunos datos de cabezal para el objeto ERP, deben ser obtenidos en el parseo de líneas pazos.
228 2. Arma una lista con las líneas del ticket definidas como `lineas de interés` en la configuración.
229 3. Con cada ticket invoca al parser de líneas pasándole (cabezal, líneas) como parámetros.
231 :return: tcks_del_dia: lista de tuplas [(cant_movimientos, tickets_del_dia)]
235 tickets = self.csv2py()
236 cabezales_pazos = tickets.keys()
237 cant_tickets_procesados = 0
239 # pos_ticket_id = linea_tck_pazos_id = None
240 tcks_del_dia = list()
242 for cabezal in cabezales_pazos:
243 if cabezal[0] not in config.cabezales_interes.keys():
244 # saltear tickets que no interesan. solo lee venta/cobr, devol, ndcr
246 C = IPCabezal(list(cabezal))
247 # print C.ticket, C.caja , C.timestamp
248 # :debug: reemplazar numero de ticket y nro. de caja
249 # if C.ticket == 84174 and C.caja == 1: import ipdb;ipdb.set_trace()
250 # print C.llave, C.descripcion
251 # se CREA "pos_ticket", miembro del cabezal "C"
252 # extrayendo/simplificando solo datos útiles de "C".
255 C.pos_ticket = dict( # será el cabezal del ticket al que luego se agregarán datos
256 # disponibles en las líneas.
259 sucursal_id = sucursal,
260 tipocabezal = C.llave,
261 descripcion_cab = C.descripcion,
262 timestamptck = datetime.strptime(C.timestamp, "%Y%m%d%H%M%S").strftime('%Y-%m-%d %H:%M:%S'),
264 numerotck = int(C.ticket),
265 codigocajera = C.nro_cajera,
266 date = C.yyyy + '-' + C.mm + '-' + C.dd,
267 estadotck = C.estado,
268 cantarticulos = int(C.cant_articulos),
269 totalapagar = round(Decimal(C.totalapagar), 2),
270 tipocliente = C.tipo_de_cliente,
271 cantidadlineas = int(C.cant_lineas),
273 redondeos_tck = list(),
274 cobranzas_tck = dict(),
275 # relaciones serán tablas relacionadas al cabezal
277 cuentas_tck = list(),
278 tarjetas_tck = list(),
279 cheques_tck = list(),
280 devenvases_tck = list(),
281 efectivo_tck = list(),
287 egresocaja_tck = list(),
288 retiros_tck = list(),
289 almuerzo_tck = list(),
293 # en el volcado de datos a un ERP deberá redefinirse el concepto de cabzal de ticket
295 # se leen todas las líneas antes de confirmar el cabezal, dado que existen líneas pueden contener
296 # información relevnte para el cabezal.
297 if C.llave in ('8', ):
299 # 8 Linea Cajera (entrea-sale-pausa)
301 lineas_tck_pazos = []
303 for linea in tickets[cabezal]:
304 # es la llave en la instancia del objeto ticket
305 if linea[1] not in config.lineas_interes.keys():
307 lineas_tck_pazos.append(linea)
308 # print("llave: %s tst: %s caja: %s ticket: %s" % (C.llave, ttst14, caja, ticket))
309 # si hay tickets con líneas.
310 if isinstance(C, IPCabezal) and len(lineas_tck_pazos):
312 # ############################################################
313 lineas_ticket = parse_lines(C.pos_ticket, lineas_tck_pazos)
314 tcks_del_dia.append((C.pos_ticket, lineas_ticket))
317 if C.pos_ticket['tipocabezal'] == '1' and 'tipo_de_ticket_descrip' in C.pos_ticket:
318 cant_tickets_procesados += 1
320 # print '%s ' % (tickets_procesados,),
321 elif C.pos_ticket['tipocabezal'] in ('3', '16') and 'tipo_de_ticket_descrip' in \
323 # 3 modo devolución se anula algo previamente vendido
324 # 16 Ticket Nota de Credito Devolucion devol. un ticket de venta con factura.
325 # Es un cabezal indicador cuyos totales estan en 0. -ver línea de detalla tipo 92
326 cant_tickets_procesados -= 1
328 # print '\n\t devol : %s %s %s' % (cant_tickets_procesados, C.pos_ticket[
329 # 'tipocabezal'], C.pos_ticket['tipo_de_ticket_descrip']),
331 except Exception as err_msg:
332 msg = "[ERROR]: Error al parsear: %s %s %s" % (err_msg, C.llave, C.descripcion)
333 print(msg + ' adentro')
334 import ipdb;ipdb.set_trace()
337 msg = " %s Ticket informado con errores %s Ver Salidapazos." % (
338 ns, C.timestamp + C.caja + C.ticket)
341 # except Exception as ex:
343 # import ipdb;ipdb.set_trace()
345 return cant_tickets_procesados, tcks_del_dia
350 #TODO: la desripción de pasos 1-6 es obsoleta 03/11/2019
351 :return: crea los archivo/s .json "tckAAAADDMM.json"
353 Procesa informes de salida de los POS, filtra movimientos
354 según criterios configurables, serializa la salida en
355 estructuras `json` que escribe en un archivo.
357 1. lee el directorio de salidapazos
358 2. prepara la lista de archivos salidapazos a procesar
359 3. lee los informes del filesystem
360 4. procesa cada informe salidapazos
361 5. crea archivos json correspondientes a cada informe
362 6. marca los informes procesaros en "/admi/pazos4/.sqlite3.db"
364 Los archivos json tiene la forma:
367 ({lineas_cabezal},{lineas_detalle}),
368 ({lineas_cabezal},{lineas_detalle}),
369 ({lineas_cabezal},{lineas_detalle})
373 # import ipdb;ipdb.set_trace()
376 sqlite_inf_obj = SqliteTckJson()
378 infofile = sqlite_inf_obj.json_a_procesar()
379 # print len(infofile)
383 msg = 'No hay informes sin procesar!'
384 print(" %s : %s" % (ns, msg))
385 logging.warn(" %s [ Warninig ] : %s " % (ns, msg))
389 fecha = get_date(i) # es la fecha en que pazos4 emitió el informe.
390 _date = "%s-%s-%s" % (fecha[0:4], fecha[4:6], fecha[6:8])
395 info_obj = OpenTck(i)
396 cntctk, tcks_del_dia = info_obj.passtck
398 # informe = info_obj.csv2py() # levanta Salidapazos_nuevo (csv) y crea un diccionario
400 # import ipdb;ipdb.set_trace()
401 # cntctk, tcks_del_dia = info_obj.passtck()
402 # tcks_del_dia es un objeto python con todos los tickets de un día/informe
404 _date = "%s-%s-%s" % (fecha[0:4], fecha[4:6], fecha[6:8])
406 # #### 5. serializa python to json y escribe el json file
407 json_file = "%stck%s.json" % (oPath, fecha,)
410 with open(json_file, 'w') as fp:
411 json.dump(tcks_del_dia, fp, separators=(',', ': '), indent=3)
413 # #### 6. resgister_json_history_db
414 sqlite_inf_obj.resgister_json_history_db(i, fecha, json_file)
415 logging.info(" %s [ Info ] %s generado ok " % (ns, fecha))
416 sys.stdout.write('\b')
417 sys.stdout.write(' ')
418 print ' ', cntctk, 'ok.'
419 except Exception as escritura:
420 logging.error(" %s [ Error ] : %s Imposible escribir el informe ln 389" % (ns, escritura))
423 if __name__ == '__main__':
426 json_fil = "/tmp/llave124.json"
428 with open(json_fil, 'w') as fp:
429 json.dump(LLAVE124, fp, separators=(',', ': '), indent=3)
430 except Exception as escritura:
431 logging.error( " %s [ Error ] : %s Imposible escribir el informe" % (ns, escritura) )
433 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: