def main(): """ Rutina principal. """ # pylint: disable=too-many-branches # # Parámetros parser = argparse.ArgumentParser( description="Pruebas de movimientos de stock en Murano.") parser.add_argument("-b", "--balas", dest="balas", help="Inserta las últimas n balas y sus consumos.", default=0, type=int) parser.add_argument("-r", "--rollos", dest="rollos", help="Inserta los últimos n rollos y sus consumos.", default=0, type=int) parser.add_argument("-c", "--cajas", dest="cajas", help="Inserta los últimos n palés y sus consumos.", default=0, type=int) parser.add_argument("-g", "--bigbags", dest="bigbags", help="Inserta los últimos n bigbags y sus consumos.", default=0, type=int) parser.add_argument("-o", "--codigo", dest="codigo", nargs="+", help="Especifica el código del artículo a insertar. " "**No tiene en cuenta los consumos.**", default=None, type=str) parser.add_argument("-f", "--file_source", dest="file_source", help="Lee los códigos a insertar desde un fichero de" " texto." "**Realiza también los consumos relacionados.**", type=str) if len(sys.argv) == 1: parser.print_help() args = parser.parse_args() # # Pruebas if args.balas: balas = pclases.Bala.select(orderBy="-id") for bala in balas.limit(args.balas): prueba_bala(bala.codigo) if args.rollos: rollos = pclases.Rollo.select(orderBy="-id") for rollo in rollos.limit(args.rollos): prueba_rollo(rollo.codigo) if args.cajas: pales = pclases.Pale.select(orderBy="-id") for pale in pales.limit(args.cajas): prueba_pale(pale.codigo) if args.bigbags: bigbags = pclases.Bigbag.select(orderBy="-id") for bigbag in bigbags.limit(args.bigbags): prueba_bigbag(bigbag.codigo) if args.codigo: pbar = tqdm(args.codigo) for codigo in pbar: pbar.set_description("Insertando con consumos %s" % (codigo)) prueba_codigo(codigo, consumir=True) if args.file_source: pbar = tqdm(parse_file(args.file_source), total=file_len(args.file_source)) for codigo in pbar: pbar.set_description("Insertando sin consumos %s" % (codigo)) prueba_codigo(codigo, consumir=False)
def main(): """ Rutina principal. """ # pylint: disable=too-many-branches # # Parámetros parser = argparse.ArgumentParser(description="Soy el Sr. Lobo. Soluciono problemas.") parser.add_argument( "-a", "--articulos", dest="codigos_articulos", help="Códigos de artículos a comprobar.", nargs="+", default=[] ) parser.add_argument( "-p", "--productos", dest="codigos_productos", help="Códigos de productos a comprobar.", nargs="+", default=[] ) parser.add_argument( "-n", "--dry-run", dest="simulate", help="Simular. No hace cambios en la base de datos.", default=False, action="store_true", ) ahora = datetime.datetime.today().strftime("%Y%m%d_%H") parser.add_argument( "-o", dest="fsalida", help="Guardar resultados en fichero de salida.", default="%s_sr_lobo.txt" % (ahora) ) parser.add_argument( "-v", "--view", dest="ver_salida", help="Abre el fichero de salida en un editor externo.", default=False, action="store_true", ) parser.add_argument( "-c", "--consumos", dest="consumos", help="Realiza los consumos atrasados", default=False, action="store_true" ) args = parser.parse_args() if args.ver_salida: if not os.path.exists(args.fsalida): open(args.fsalida, "a").close() subprocess.Popen('gvim "{}"'.format(args.fsalida)) # Primero termino de procesar todas las posibles imortaciones pendientes: finish_pendientes(args.fsalida, args.simulate) # Y corrijo las posibles dimensiones nulas: corregir_dimensiones_nulas(args.fsalida, args.simulate) if not args.codigos_articulos and not args.codigos_productos and not args.consumos: # Si no recibo argumentos, compruebo todos los artículos y productos. args.codigos_articulos, args.codigos_productos = check_everything(args.fsalida) # Y los consumos args.consumos = True # # Pruebas if args.codigos_productos: for codigo in tqdm(args.codigos_productos, desc="Productos"): sync_producto(codigo, args.fsalida, args.simulate) if args.codigos_articulos: for codigo in tqdm(args.codigos_articulos, desc="Artículos"): sync_articulo(codigo, args.fsalida, args.simulate) # # Consumos if args.consumos: make_consumos(args.fsalida, args.simulate)
def check_everything(fsalida): """ Devuelve todos los códigos de artículos que hay en el almacén en ginn (eso incluye, por fuerza, todo lo fabricado después del 31 de mayo de 2016, que fue cuando se hizo la migración). Pero de todos modos devolverá también todos esos artículos por si alguien ha consumido balas en partidas de carga o ha hecho un albarán de salida por lo que sea. Devuelve también todos los códigos de productos **de Murano**, que es donde se mantienen. Así comprobará después que en ginn existen y tienen la misma información. """ report = open(fsalida, "a", 0) # Sync artículos. ginn => Murano fini = datetime.datetime( 2016, 5, 31, 17, 30) - datetime.timedelta(hours=17.5) report.write("Buscando todos los artículos... ") # pylint: disable=bad-continuation, no-member articulos_en_almacen = pclases.Articulo.select( # NOQA pclases.Articulo.q.almacen != None) partes_fabricacion = pclases.ParteDeProduccion.select( pclases.ParteDeProduccion.q.fechahorainicio >= fini) articulos = set(articulos_en_almacen) for pdp in tqdm(partes_fabricacion, total=partes_fabricacion.count(), unit="pdp", desc="Partes de producción"): articulos.update(set(pdp.articulos)) # Completo con balas y rollos C, que no tienen parte de producción: balas_cable = pclases.BalaCable.select( pclases.BalaCable.q.fechahora >= fini) for bc in tqdm(balas_cable, total=balas_cable.count(), unit="bc", desc="Balas de cable"): articulos.add(bc.articulo) rollos_c = pclases.RolloC.select(pclases.RolloC.q.fechahora >= fini) for rc in tqdm(rollos_c, total=rollos_c.count(), unit="rc", desc="Rollos C"): articulos.add(rc.articulo) report.write("{} encontrados. Ordenando...\n".format(len(articulos))) larticulos = list(articulos) larticulos.sort(key=lambda a: a.id, reverse=True) codigos_articulos = [a.codigo for a in larticulos] # Sync productos de compra y venta. ginn <= Murano report.write("Buscando todos los productos de venta... ") conn = murano.connection.Connection() sql = "SELECT CodigoArticulo FROM {}.dbo.Articulos".format( conn.get_database()) sql += " WHERE CodigoArticulo LIKE 'P%'" sql += " AND CodigoEmpresa = '{}';".format(murano.connection.CODEMPRESA) productos = conn.run_sql(sql) codigos_productos = [r['CodigoArticulo'] for r in productos] report.write("{} encontrados.\n".format(len(codigos_productos))) report.close() return codigos_articulos, codigos_productos
def finish_pendientes(fsalida, simulate=True): """ Busca todos los registros de importaciones pendientes de procesar y las completa si simulate viene a False. """ report = open(fsalida, "a", 0) conn = murano.connection.Connection() sql = """SELECT IdProcesoIME FROM {}.dbo.Iniciador_TmpIME WHERE FechaFin IS NULL;""".format(conn.get_database()) guids = conn.run_sql(sql) report.write("{} procesos pendientes encontrados.\n".format(len(guids))) for proceso in tqdm(guids, desc="Procesos pendientes"): guid = proceso['IdProcesoIME'] if not simulate: report.write("Procesando {}...".format(guid)) res = murano.ops.fire(guid) else: report.write("Simulando {}...".format(guid)) res = True if res: report.write(" [OK]\n") else: report.write(" [KO]\n") report.close()
def make_consumos_materiales(fsalida, simulate=True, fini=None, ffin=None): """ Recorre todos los consumos entre la fecha inicial y la final. Para cada consumo realiza el rebaje de stock en Murano mediante un movimiento de salida y marca el _flag_ `api` a True para indicarlo. Si simualte es True, no hace nada y solo actualiza el log de `fsalida`. Devuelve True si todos los consumos pendientes se han realizado. False si alguno de ellos ha dado error. """ # Check de parámetros report = open(fsalida, "a", 0) if not fini: fini = datetime.date(2016, 5, 31) # Fecha en que entró Murano. if not ffin: ffin = datetime.date.today() + datetime.timedelta(days=1) # pylint: disable=no-member, singleton-comparison pdps = pclases.ParteDeProduccion.select(pclases.AND( pclases.ParteDeProduccion.q.fechahorainicio >= fini, pclases.ParteDeProduccion.q.fechahorafin <= ffin, pclases.ParteDeProduccion.q.bloqueado == True)) # noqa report.write("{} partes encontrados.\n".format(pdps.count())) res = True for pdp in tqdm(pdps, total=pdps.count(), desc="Partes a consumir (materia prima)", unit="pdp"): for consumo in pdp.consumos: # Ignoro los ya tratados y los que no se completaron en ginn. if not consumo.api and consumo.actualizado: res = consumir_mp(consumo, report, simulate) and res report.close() return res
def make_consumos_bigbags(fsalida, simulate=True, fini=None, ffin=None): """ Realiza los consumos de fibra en bigbag por parte de la línea de cemento. """ # Check de parámetros report = open(fsalida, "a", 0) if not fini: fini = datetime.date(2016, 5, 31) # Fecha en que entró Murano. if not ffin: ffin = datetime.date.today() + datetime.timedelta(days=1) pdps = pclases.ParteDeProduccion.select( pclases.AND( pclases.ParteDeProduccion.q.fechahorainicio >= fini, pclases.ParteDeProduccion.q.fechahorafin <= ffin, pclases.ParteDeProduccion.q.bloqueado == True, ) ) report.write("{} partes encontrados.\n".format(pdps.count())) res = True for pdp in tqdm(pdps, total=pdps.count(), desc="Partes a consumir (bigbags)", unit="pdp"): for bb in pdp.bigbags: # Ignoro los ya tratados y los que no se completaron en ginn. if not bb.api: res = consumir_bigbag(report, bb, simulate) and res report.close() return res
def insertar_pcs(guid_proceso): """ Selecciona todos los productos de compra y devuelve una lista de instrucciones SQL de movimiento de stock para lanzarlas contra las TmpIME de MSSQLServer. """ res = [] prodscompra = pclases.ProductoCompra.select( pclases.ProductoCompra.q.existencias > 0) for pc in tqdm(prodscompra, total=prodscompra.count()): for almacen in pclases.Almacen.select(): cantidad = pc.get_existencias(almacen) if cantidad > 0: try: sqls = murano.ops.update_stock(pc, cantidad, almacen, guid_proceso=guid_proceso, simulate=True) except (AssertionError, IndexError): print("El producto PC{} ({}) no se encuentra " "en Murano.".format(pc.id, pc.descripcion), file=sys.stderr) continue for sql in sqls: res.append(sql) return res
def main(): """ Rutina principal. """ # # Parámetros parser = argparse.ArgumentParser( description="Soy Ted Codd abrazado a mi premio Turing revolviéndome" " en mi tumba.") parser.add_argument("-p", "--productos", dest="codigos_productos", help="Códigos de productos a comprobar.", nargs="+", default=[]) parser.add_argument("-n", "--dry-run", dest="simulate", help="Simular. No hace cambios en la base de datos.", default=False, action='store_true') ahora = datetime.datetime.today().strftime("%Y%m%d_%H") parser.add_argument("-o", dest="fsalida", help="Guardar resultados en fichero de salida.", default="%s_sr_lobo.txt" % (ahora)) parser.add_argument("-v", "--view", dest="ver_salida", help="Abre el fichero de salida en un editor externo.", default=False, action='store_true') args = parser.parse_args() if args.ver_salida: if not os.path.exists(args.fsalida): open(args.fsalida, 'a').close() subprocess.Popen('gvim "{}"'.format(args.fsalida)) # # Pruebas if args.codigos_productos: for codigo in tqdm(args.codigos_productos, desc="Productos"): fix_stock_producto(codigo, args.fsalida, args.simulate)
def insertar_pvs(guid_proceso): """ Selecciona todos los artículos de productos de venta y devuelve la lista de instrucciones SQL a ejecutar para lanzarlas contra las tablas TmpIME de MSSQLServer. Cada código de artículo se compone de 2 instrucciones SQL: movimiento de serie y movimiento de stock. """ res = [] # pylint: disable=singleton-comparison articulos = pclases.Articulo.select(pclases.AND( pclases.Articulo.q.api == False, # NOQA pclases.Articulo.q.almacen != None)) # NOQA for articulo in tqdm(articulos, total=articulos.count()): try: obs = "[dump_existencias] Volcado inicial." sqls = murano.ops.create_articulo(articulo, guid_proceso=guid_proceso, simulate=True, observaciones=obs) # pylint: disable=broad-except except Exception as e: strer = "El artículo {} no se pudo insertar. Excepción: {}".format( articulo.puid, e) logging.error(strer) print(strer, file=sys.stderr) sqls = None if sqls: for sql in sqls: res.append(sql) else: logging.warning("El artículo %s (%s) no generó SQL." " ¿Ya existe en Murano?", articulo.puid, articulo.codigo) return res
def ejecutar_proceso(connection, sql_pv, sql_pc, guid_proceso): """ Ejecuta todas las instrucciones INSERT de productos de venta y de compra y posteriormente lanza el proceso de importación de Murano. """ sqls = sql_pv + sql_pc for sql in tqdm(sqls): connection.run_sql(sql) res = murano.ops.fire(guid_proceso, ignore_errors=True) return res
def make_consumos_bigbags(fsalida, simulate=True, fini=None, ffin=None): """ Realiza los consumos de fibra en bigbag por parte de la línea de cemento. """ # Check de parámetros report = open(fsalida, "a", 0) if not fini: fini = datetime.date(2016, 5, 31) # Fecha en que entró Murano. if not ffin: ffin = datetime.date.today() + datetime.timedelta(days=1) # pylint: disable=no-member, singleton-comparison pdps = pclases.ParteDeProduccion.select(pclases.AND( pclases.ParteDeProduccion.q.fecha >= fini, pclases.ParteDeProduccion.q.fecha < ffin, pclases.ParteDeProduccion.q.bloqueado == True)) # noqa report.write("Consumos bigbag: {} partes encontrados.\n".format( pdps.count())) res = True for pdp in tqdm(pdps, total=pdps.count(), desc="Partes a consumir (bigbags)", unit="pdp"): for bb in pdp.bigbags: # Ignoro los ya tratados y los que no se completaron en ginn (el # parte está sin verificar). if not bb.api: if murano.ops.esta_en_almacen(bb.articulo) == "GTX": res = consumir_bigbag(report, bb, simulate) and res else: # El bigbag está en otro almacén o no está. Me da igual. # No lo puedo consumir ni se podrá consumir jamás. Hay que # corregirlo a mano. report.write("El bigbag {} está en otro almacén, no está " "o ya se ha consumido y el valor `api` está " "mal.\n".format(bb.codigo)) res = False if not simulate and murano.ops.esta_consumido(bb.articulo): # ¿Esta consumido en el mismo parte en _ginn_ y # _Murano_? mov = murano.ops.get_ultimo_movimiento_articulo_serie( murano.connection.Connection(), bb.articulo) if mov['Documento'] == bb.parteDeProduccionID: bb.api = True bb.syncUpdate() report.write(" > Corregido valor `api` de consumo" " de {}.\n".format(bb.codigo)) res = True report.close() return res
def make_consumos_balas(fsalida, simulate=True, fini=None, ffin=None): """ Realiza los consumos de balas cargadas en las partidas de carga para consumo de la línea de geotextiles. """ res = True # Check de parámetros report = open(fsalida, "a", 0) if not fini: fini = datetime.date(2016, 5, 31) # Fecha en que entró Murano. if not ffin: ffin = datetime.date.today() + datetime.timedelta(days=1) pcargas = pclases.PartidaCarga.select( pclases.AND( pclases.PartidaCarga.q.fecha >= fini, pclases.PartidaCarga.q.fecha <= ffin, pclases.PartidaCarga.q.api == False, ) ) # TODO: Debería meter un filtro más para no consumir las partidas de carga # que no tengan todos los partes de producción verificados. Así doy margen # a Jesús para que pueda modificarlas. Si no tiene partes, no volcar tampoco. report.write("{} partidas de carga encontradas.\n".format(pcargas.count())) for pcarga in tqdm(pcargas, total=pcargas.count(), desc="Partidas de carga", unit="partida"): respartida = True if not pcarga.balas: # Partida de carga vacía. No modifico `api` report.write("Partida de carga {} vacía. Se ignora.\n".format(pcarga.codigo)) continue for bala in pcarga.balas: report.write( "Consumiendo bala {} [id {} ({})] de {}:\n".format( bala.codigo, bala.id, bala.articulo.puid, bala.articulo.productoVenta.descripcion ) ) # Aquí hacemos efectivo el rebaje de stock _res = murano.ops.consume_bala(bala, simulate=simulate) report.write("\tValor de retorno: {}\n".format(_res)) respartida = respartida and _res if respartida and not simulate: pcarga.api = True pcarga.sync() report.write("\tValor api partida de carga actualizado.\n") report.close() return res
def corregir_dimensiones_nulas(fsalida, simulate=True): """ En lugar de buscar en ginn todos los artículos y recorrerlos para comprobarlos en Murano, buscamos en Murano los artículos mal traspasados y los corregimos según lo que tengan en ginn (más rápido). Este preproceso acelerará también los posteriores. """ report = open(fsalida, "a", 0) conn = murano.connection.Connection() sqls = ("""SELECT CodigoArticulo, NumeroSerieLc, PesoBruto_, PesoNeto_, MetrosCuadrados FROM {}.dbo.ArticulosSeries WHERE CodigoEmpresa = '{}' AND NumeroSerieLc LIKE 'B%' AND (PesoBruto_ = 0.0 OR PesoNeto_ = 0.0); -- Balas (A y B) """, """SELECT CodigoArticulo, NumeroSerieLc, PesoBruto_, PesoNeto_, MetrosCuadrados FROM {}.dbo.ArticulosSeries WHERE CodigoEmpresa = '{}' AND NumeroSerieLc LIKE 'Z%' AND (PesoBruto_ = 0.0 OR PesoNeto_ = 0.0); -- Balas de cable (C) """, """SELECT CodigoArticulo, NumeroSerieLc, PesoBruto_, PesoNeto_, MetrosCuadrados FROM {}.dbo.ArticulosSeries WHERE CodigoEmpresa = '{}' AND NumeroSerieLc LIKE 'C%' AND (PesoBruto_ = 0.0 OR PesoNeto_ = 0.0); -- Bigbag (A, B, C) """, """SELECT CodigoArticulo, NumeroSerieLc, PesoBruto_, PesoNeto_, MetrosCuadrados, CodigoPale FROM {}.dbo.ArticulosSeries WHERE CodigoEmpresa = '{}' AND NumeroSerieLc LIKE 'J%' AND (PesoBruto_ = 0.0 OR PesoNeto_ = 0.0 OR CodigoPale = ''); -- Cajas (A, B, C) """, """SELECT CodigoArticulo, NumeroSerieLc, PesoBruto_, PesoNeto_, MetrosCuadrados FROM {}.dbo.ArticulosSeries WHERE CodigoEmpresa = '{}' AND NumeroSerieLc LIKE 'R%' AND (MetrosCuadrados = 0.0 OR PesoBruto_ = 0.0 OR PesoNeto_ = 0.0); -- Rollos (A) """, """SELECT CodigoArticulo, NumeroSerieLc, PesoBruto_, PesoNeto_, MetrosCuadrados FROM {}.dbo.ArticulosSeries WHERE CodigoEmpresa = '{}' AND NumeroSerieLc LIKE 'X%' AND (MetrosCuadrados = 0.0 OR PesoBruto_ = 0.0 OR PesoNeto_ = 0.0); -- Rollos defectuosos (B) """, """SELECT CodigoArticulo, NumeroSerieLc, PesoBruto_, PesoNeto_, MetrosCuadrados FROM {}.dbo.ArticulosSeries WHERE CodigoEmpresa = '{}' AND NumeroSerieLc LIKE 'Y%' AND (PesoBruto_ = 0.0 OR PesoNeto_ = 0.0); -- Rollos C """) i = 1 tot = len(sqls) res = True for sql in tqdm(sqls, total=tot, desc="Dimensiones nulas"): sql = sql.format(conn.get_database(), murano.connection.CODEMPRESA) codigos = conn.run_sql(sql) report.write("{}/{}: {} artículos encontrados:\n".format(i, tot, len(codigos))) for registro in tqdm(codigos, leave=False, desc="SQL {}".format(i)): codigo = registro['NumeroSerieLc'] res = sync_articulo(codigo, fsalida, simulate) and res i += 1 report.close() return res
def main(): """ Rutina principal. """ # # Parámetros tini = time.time() parser = argparse.ArgumentParser( description="Soy el inspector Clouseau.\n" "Le sigo la pista a cada uno de los bultos que se han " "fabricado, vendido, consumido o ajustado para ver si hay" " alguna posible desviación.\n" "La pantera rosa no se ha movido del almacén pricipal. " "Así que de momento solo investigo ahí.") def_fich_ini = '.' parser.add_argument("--fichero_stock_inicial", dest="fich_inventario", default=def_fich_ini) parser.add_argument("-p", "--productos", dest="codigos_productos", help="Códigos de productos a comprobar.", nargs="+", default=[]) ahora = datetime.datetime.today().strftime("%Y%m%d_%H") parser.add_argument("-o", dest="fsalida", help="Guardar resultados en fichero de salida.", default="%s_clouseau.md" % (ahora)) parser.add_argument("-v", "--view", dest="ver_salida", help="Abre el fichero de salida en un editor externo.", default=False, action='store_true') parser.add_argument("-d", "--debug", dest="debug", help="Modo depuración.", default=False, action="store_true") parser.add_argument("--ffin", dest='ffin', help="Fecha fin (no incluida)", default=None) args = parser.parse_args() if args.debug: connection.DEBUG = True if args.ver_salida: if not os.path.exists(args.fsalida): open(args.fsalida, 'a').close() subprocess.Popen(["gvim", args.fsalida]) # # Pruebas productos = [] results = [] # Resultados de las comprobaciones para cada `productos`. if args.codigos_productos: for codigo in tqdm(args.codigos_productos, desc="Buscando productos ginn"): producto = murano.ops.get_producto_ginn(codigo) productos.append(producto) else: for pv in tqdm(pclases.ProductoVenta.select(orderBy="id"), desc="Buscando productos ginn"): productos.append(pv) fich_inventario = find_fich_inventario(args.fich_inventario) fini = parse_fecha_xls(fich_inventario) today = datetime.date.today() # Para evitar problemas con las fechas que incluyen horas, y para que # éstas entren en el intervalo, agrego un día a la fecha final y hago # el filtro con menor estricto: una producción del 02/01/17 23:00 # la consideramos como que entra en el día 2, y entraría en el filtro # [01/01/17..02/01/17] porque en realidad sería [01/01/17..03/01/17). if not args.ffin: ffin = today + datetime.timedelta(days=1) else: if "/" in args.ffin: ffin = datetime.date(*[int(a) for a in args.ffin.split("/")[::-1]]) else: if int(args.ffin[:4]) >= 2017: args.ffin = args.ffin[6:] + args.ffin[4:6] + args.ffin[:4] ffin = datetime.date(day=int(args.ffin[:2]), month=int(args.ffin[2:4]), year=int(args.ffin[4:])) report = open(args.fsalida, "a", 0) report.write("Analizando desde {} a {}, fin no incluido.\n".format( fini.strftime("%d/%m/%Y"), ffin.strftime("%d/%m/%Y"))) report.write("==========================================================" "\n") report.write("## Todas las cantidades son en (bultos, m², kg).\n") data_inventario = load_inventario(fich_inventario, "Desglose") data_res = tablib.Dataset(title="Incoherencias") data_full = tablib.Dataset(title="Detalle") data_res.headers = ['Código', 'Producto', 'Serie', 'Calidad', 'Prod. ginn', 'Prod. Murano', 'Cons. ginn', 'Cons. Murano', 'Bultos', 'm²', 'kg'] data_full.headers = ['Código', 'Producto', 'Serie', 'Calidad', 'Bultos', 'm²', 'kg', 'Prod. ginn', 'Prod. Murano', 'Origen', 'Fabricado en', 'Cons. ginn', 'Cons. Murano', 'Consumido en', 'Venta', 'Vendido en'] for producto in tqdm(productos, desc="Productos"): res = investigar(producto, fini, ffin, report, data_res, data_full, data_inventario, args.debug) results.append((producto, res)) fallos = [p for p in results if not p[1]] report.write("Encontrados {} productos con incoherencias: {}".format( len(fallos), "; ".join(['PV{}'.format(p[0].id) for p in fallos]))) report.write("\n\nFecha y hora de generación del informe: {}\n".format( datetime.datetime.now().strftime("%d/%m/%Y %H:%M"))) tfin = time.time() hours, rem = divmod(tfin - tini, 3600) minutes, seconds = divmod(rem, 60) report.write("Tiempo transcurrido: {:0>2}:{:0>2}:{:05.2f}\n".format( int(hours), int(minutes), seconds)) report.write("\n\n___\n\n") report.close() fout = args.fsalida.replace(".md", ".xls") book = tablib.Databook((data_res, data_full)) with open(fout, 'wb') as f: # pylint: disable=no-member f.write(book.xls)
def investigar(producto_ginn, fini, ffin, report, data_res, data_full, data_inventario, dev=False): """ Para ver qué series están provocado la desviación, coge los bultos iniciales y los producidos durante el periodo, y analiza uno por uno si han sido vendidos, consumidos, ajustados o siguen en almacén. Las producciones y los consumos los hace por partida doble: en ERP y en Murano. Así sabremos si la desviación está en la API. """ res = True # 0.- Localizo los consumos y producciones por calidad. consumos_ginn = buscar_bultos_consumidos_ginn(producto_ginn, fini, ffin) consumos_murano = buscar_bultos_consumidos_murano( producto_ginn, fini, ffin) producciones_ginn = buscar_bultos_producidos_ginn( producto_ginn, fini, ffin) producciones_murano = buscar_bultos_producidos_murano(producto_ginn, fini, ffin) # 1,- La investigasió ginn_no_murano = {"consumos": {}, "producción": {}} murano_no_ginn = {"consumos": {}, "producción": {}} # 1.0.0- Todos los artículos del inventario anterior. codigo_producto_murano = "PV{}".format(producto_ginn.id) for row_inventario in tqdm(data_inventario.dict, desc="Artículos inventario anterior"): if row_inventario[u'Código producto'] == codigo_producto_murano: codigo = row_inventario[u'Código trazabilidad'] articulo = pclases.Articulo.get_articulo(codigo) add_to_datafull(articulo, data_full, row_inventario) # 1.1.- Los que están consumidos/fabricados en ginn pero no en Murano for dic_ginn, dic_murano, category in ( (consumos_ginn, consumos_murano, "consumos"), (producciones_ginn, producciones_murano, "producción")): for calidad in dic_ginn: for articulo in tqdm(dic_ginn[calidad], desc="Artículos solo en ginn"): add_to_datafull(articulo, data_full) if (calidad not in dic_murano or articulo.codigo not in dic_murano[calidad]): try: ginn_no_murano[category][calidad].append( articulo.codigo) except KeyError: ginn_no_murano[category][calidad] = [articulo.codigo] # 1.2.- Los que se han consumido/fabricado en Murano pero no en ginn for calidad in dic_murano: for codigo in tqdm(dic_murano[calidad], desc="Artículos solo en Murano"): add_to_datafull(codigo, data_full) if (calidad not in dic_ginn or codigo not in [a.codigo for a in dic_ginn[calidad]]): try: murano_no_ginn[category][calidad].append(codigo) except KeyError: murano_no_ginn[category][calidad] = [codigo] # 2.- Cabecera del informe de resultados: if not dev: # pylint: disable=no-member try: producto_murano = murano.ops.get_producto_murano("PV{}".format( producto_ginn.id)) report.write("{}: {}\n".format(producto_murano.CodigoArticulo, producto_ginn.descripcion)) except AttributeError: report.write("{}: _({}) {}_\n".format( "***¡Producto no encontrado en Murano!***", producto_ginn.puid, producto_ginn.descripcion)) else: report.write("PV{}: {}\n".format(producto_ginn.id, producto_ginn.descripcion)) # 3.- Escribo los resultados al report. # 3.1.- Consumos en ginn no volcados a Murano. report.write("## Artículos consumidos en ginn pero no en Murano\n") for calidad in ginn_no_murano["consumos"]: report.write("### Calidad {}:\n".format(calidad)) for codigo in ginn_no_murano["consumos"][calidad]: articulo = pclases.Articulo.get_articulo(codigo) if articulo.es_bala(): parte_o_partidacarga = articulo.bala.partidaCarga.codigo fecha_consumo = articulo.bala.partidaCarga.fecha.strftime( "%d/%m/%Y %H:%M") elif articulo.es_bigbag(): bigbag = articulo.bigbag parte_o_partidacarga = bigbag.parteDeProduccion.id bbpdp = bigbag.parteDeProduccion fecha_consumo = bbpdp.fechahorainicio.strftime( "%d/%m/%Y %H:%M") else: parte_o_partidacarga = "¿?" fecha_consumo = "¿?" report.write(" * {} (Consumido el {} en {})\n".format( articulo.codigo, fecha_consumo, parte_o_partidacarga)) # 5.- Guardo los resultados en el Dataset para exportarlos después. # ['Código', 'Producto', 'Serie', 'Calidad', 'Cons. ginn', # 'Cons. Murano', 'Prod. ginn', 'Prod. Murano', 'Bultos', 'm²', # 'kg'] data_res.append(['PV{}'.format(producto_ginn.id), producto_ginn.descripcion, articulo.codigo, calidad, "", "", fecha_consumo, "", 1, articulo.get_superficie(), articulo.peso_neto]) # 3.2.- Consumos en Murano pero no en ginn. report.write("## Artículos consumidos en Murano pero no en ginn\n") for calidad in murano_no_ginn["consumos"]: report.write("### Calidad {}:\n".format(calidad)) for codigo in murano_no_ginn["consumos"][calidad]: # 5.- Guardo los resultados en el Dataset para exportarlos después. # ['Código', 'Producto', 'Serie', 'Calidad', 'Cons. ginn', # 'Cons. Murano', 'Prod. ginn', 'Prod. Murano', 'Bultos', 'm²', # 'kg'] articulo = pclases.Articulo.get_articulo(codigo) fecha_consumo = murano.ops.esta_consumido(articulo) if fecha_consumo: # pylint: disable=no-member fecha_consumo = fecha_consumo.strftime("%d/%m/%Y %H:%M") report.write(" * {} (Volcado como consumo el {})\n".format( codigo, fecha_consumo)) # pylint: disable=protected-access superficie = murano.ops._get_superficie_murano(articulo) peso_neto = murano.ops._get_peso_neto_murano(articulo) if codigo not in data_res['Serie']: data_res.append(['PV{}'.format(producto_ginn.id), producto_ginn.descripcion, codigo, calidad, "", "", "", fecha_consumo, 1, superficie, peso_neto]) else: index = data_res['Serie'].index(codigo) row = list(data_res[index]) row[7] = fecha_consumo row[9] = superficie row[10] = peso_neto data_res[index] = row # 3.3.- Producciones en ginn pero no en Murano. report.write("## Artículos fabricados en ginn pero sin alta en Murano " "en el mismo periodo\n") for calidad in ginn_no_murano["producción"]: report.write("### Calidad {}:\n".format(calidad)) for codigo in ginn_no_murano["producción"][calidad]: articulo = pclases.Articulo.get_articulo(codigo) fecha_produccion = articulo.parteDeProduccion.fechahorainicio superficie = articulo.get_superficie() peso_neto = articulo.peso_neto report.write(" * {} (Fabricada el {} en el parte del {})\n".format( articulo.codigo, articulo.fechahora, fecha_produccion)) if codigo not in data_res['Serie']: data_res.append(['PV{}'.format(producto_ginn.id), producto_ginn.descripcion, codigo, calidad, fecha_produccion.strftime("%d/%m/%Y %H:%M"), "", "", "", 1, superficie, peso_neto]) else: index = data_res['Serie'].index(codigo) row = list(data_res[index]) row[4] = fecha_produccion.strftime("%d/%m/%Y %H:%M") row[9] = superficie row[10] = peso_neto data_res[index] = row # 3.4.- Producciones en Murano pero no en ginn. report.write( "## Artículos con alta en Murano pero no fabricados en ginn\n") for calidad in murano_no_ginn["producción"]: report.write("### Calidad {}:\n".format(calidad)) for codigo in murano_no_ginn["producción"][calidad]: articulo = pclases.Articulo.get_articulo(codigo) fecha_produccion = murano.ops.get_fecha_entrada(articulo) report.write(" * {} (Volcada como producción en Murano el {})" "\n".format(codigo, fecha_produccion)) # 5.- Guardo los resultados en el Dataset para exportarlos después. # ['Código', 'Producto', 'Serie', 'Calidad', 'Cons. ginn', # 'Cons. Murano', 'Prod. ginn', 'Prod. Murano', 'Bultos', 'm²', # 'kg'] # pylint: disable=protected-access if articulo: superficie = murano.ops._get_superficie_murano(articulo) peso_neto = murano.ops._get_peso_neto_murano(articulo) else: superficie = "N/D" peso_neto = "N/D" if codigo not in data_res['Serie']: data_res.append(['PV{}'.format(producto_ginn.id), producto_ginn.descripcion, codigo, calidad, "", fecha_produccion and fecha_produccion.strftime( "%d/%m/%Y %H:%M") or "N/D", "", "", 1, superficie, peso_neto]) else: index = data_res['Serie'].index(codigo) row = list(data_res[index]) row[5] = fecha_produccion.strftime("%d/%m/%Y %H:%M") row[9] = superficie row[10] = peso_neto data_res[index] = row # 3.5.- Fin del report para el producto. res = _is_empty(ginn_no_murano) and _is_empty(murano_no_ginn) report.write("-"*70) if res: report.write(" _[OK]_ \n") else: report.write(" **[KO]**\n") report.write("\n") # 6.- Y devuelvo si todo cuadra (True) o hay alguna desviación (False) return res
def main(): """ Rutina principal. """ # pylint: disable=too-many-branches # # Parámetros parser = argparse.ArgumentParser( description="Soy el Sr. Lobo. Soluciono problemas.") parser.add_argument("-a", "--articulos", dest="codigos_articulos", help="Códigos de artículos a comprobar.", nargs="+", default=[]) parser.add_argument("-p", "--productos", dest="codigos_productos", help="Códigos de productos a comprobar. Si no se " "especifica ningún código, se comprobarán todos.", nargs="*", default=[]) parser.add_argument("-n", "--dry-run", dest="simulate", help="Simular. No hace cambios en la base de datos.", default=False, action='store_true') ahora = datetime.datetime.today().strftime("%Y%m%d_%H") parser.add_argument("-o", dest="fsalida", help="Guardar resultados en fichero de salida.", default="%s_sr_lobo.txt" % (ahora)) parser.add_argument("-v", "--view", dest="ver_salida", help="Abre el fichero de salida en un editor externo.", default=False, action='store_true') parser.add_argument("-c", "--consumos", dest="consumos", help="Realiza los consumos atrasados", default=False, action='store_true') parser.add_argument("-t", "--todo", dest="todo", help="Ejecuta todos los chequeos.", default=False, action="store_true") args = parser.parse_args() if args.ver_salida: if not os.path.exists(args.fsalida): open(args.fsalida, 'a').close() subprocess.Popen('gvim "{}"'.format(args.fsalida)) # # Pruebas check_unidades_series_positivas() # ¿Todos los productos? if args.codigos_productos == []: # Todos los productos si no `-p PV*...` o si `-p` solamente. # (Es decir, siempre). codigos_productos = _todos_los_codigos_de_producto() else: codigos_productos = args.codigos_productos # Primero termino de procesar todas las posibles imortaciones pendientes: finish_pendientes(args.fsalida, args.simulate) # Y corrijo las posibles dimensiones nulas: corregir_dimensiones_nulas(args.fsalida, args.simulate) if args.todo: # Si `--todo` Compruebo todos los artículos y productos. print("Comprobando TODOS los artículos y productos...") args.codigos_articulos, args.codigos_productos = check_everything( args.fsalida) # Y los consumos args.consumos = True # ## Productos if codigos_productos: for codigo in tqdm(codigos_productos, desc="Productos"): sync_producto(codigo, args.fsalida, args.simulate) # ## Consumos if args.consumos: make_consumos(args.fsalida, args.simulate) if args.codigos_articulos: cachedb = CacheDB() for codigo in tqdm(args.codigos_articulos, desc="Artículos"): # Con force a False tiraré de "caché". sync_articulo(codigo, args.fsalida, args.simulate, force=False, cachedb=cachedb) cachedb.close()
def make_consumos_balas(fsalida, simulate=True, fini=None, ffin=None): """ Realiza los consumos de balas cargadas en las partidas de carga para consumo de la línea de geotextiles. """ res = True # Check de parámetros report = open(fsalida, "a", 0) if not fini: fini = datetime.date(2016, 5, 31) # Fecha en que entró Murano. if not ffin: ffin = datetime.date.today() + datetime.timedelta(days=1) # pylint: disable=no-member, singleton-comparison pcargas = pclases.PartidaCarga.select(pclases.AND( pclases.PartidaCarga.q.fecha >= fini, pclases.PartidaCarga.q.fecha <= ffin, pclases.PartidaCarga.q.api == False)) # noqa # DONE: Debería meter un filtro más para no consumir las partidas de carga # que no tengan todos los partes de producción verificados. Así doy margen # para que puedan modificarlas. Si no tiene partes, no volcar tampoco. report.write("{} partidas de carga encontradas.\n".format(pcargas.count())) for pcarga in tqdm(pcargas, total=pcargas.count(), desc="Partidas de carga", unit="partida"): respartida = True if not pcarga.balas: # Partida de carga vacía. No modifico `api` report.write("Partida de carga {} vacía. Se ignora.\n".format( pcarga.codigo)) continue pdps_sin_verificar = [pdp for pdp in pcarga.partes_partidas if not pdp.bloqueado] if pdps_sin_verificar: report.write("Partida de carga {} con {}/{} partes sin verificar" ". Se ignora.\n".format(pcarga.codigo, len(pdps_sin_verificar), len(pcarga.partes_partidas))) continue if not pcarga.partes_partidas: report.write("Partida de carga {} sin partes de producción." "Se ignora.\n".format(pcarga.codigo)) continue for bala in pcarga.balas: report.write("Consumiendo bala {} [id {} ({})] de {}:\n".format( bala.codigo, bala.id, bala.articulo.puid, bala.articulo.productoVenta.descripcion)) # Aquí hacemos efectivo el rebaje de stock fecha_consumo = murano.ops.esta_consumido(bala.articulo) if fecha_consumo: _res = True report.write("\tBala ya consumida el {}\n".format( fecha_consumo)) else: _res = murano.ops.consume_bala(bala, simulate=simulate) report.write("\tValor de retorno: {}\n".format(_res)) respartida = respartida and _res if respartida and not simulate: pcarga.api = True pcarga.sync() report.write("\tValor api partida de carga actualizado.\n") res = res and respartida report.close() return res