Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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()
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
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
Ejemplo n.º 14
0
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)
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
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()
Ejemplo n.º 17
0
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