{ ----------------------------------------------------------------------------- MODULO : prtraza OBJETIVO : Trazabilidad del Bloque. Dado un codigo_bloque, arma el recorrido completo desde cttb00003 (inventario inicial, produccion, salidas y recepciones entre departamentos, hasta almacen) y lo presenta como una pagina HTML que se abre en el navegador del cliente. USADO POR: prprmt003 (boton "Trazabilidad") NOTA : Requiere el template 'prtraza.html' desplegado junto al programa (misma carpeta bin donde corre el .42m). ----------------------------------------------------------------------------- } IMPORT os SCHEMA smarmotech # guard one-time del PREPARE/DECLARE (evita -404 al re-trazar en la misma sesion) DEFINE m_traza_prep SMALLINT DEFINE m_rango_prep SMALLINT FUNCTION trazabilidad_bloque(p_bloque) DEFINE p_bloque STRING DEFINE eventos DYNAMIC ARRAY OF RECORD num_doc INTEGER, fecha CHAR(10), dep_no SMALLINT, dep_nom CHAR(30), cod_mov SMALLINT, mov_desc CHAR(30), estado_mov SMALLINT, cod_prod INTEGER, prod_desc CHAR(60), unidad CHAR(6), cantidad DECIMAL(18,5), medida DECIMAL(18,5), emp_no INTEGER, emp_nom CHAR(60), recep_no SMALLINT, recep_nom CHAR(30), ref_no INTEGER, estatus CHAR(1) END RECORD DEFINE i, j, found, ndeptos INT DEFINE q, cls, mvlabel, prodtxt, qtytxt, s_cant, s_med, line STRING DEFINE origen, medida_ini, desc_hero, estado_hero, aprovcls, descbloque STRING DEFINE sbflow, sbtl, sbbal, sbfull base.StringBuffer DEFINE cin base.Channel DEFINE nl CHAR(1) DEFINE med_abs, v_balance, v_aprov DECIMAL(18,5) DEFINE bal DYNAMIC ARRAY OF RECORD uni CHAR(6), entro DECIMAL(18,5), salio DECIMAL(18,5) END RECORD LET nl = ASCII 10 IF p_bloque IS NULL OR LENGTH(p_bloque CLIPPED) = 0 THEN RETURN END IF # ---------------- 1. Consultar el recorrido ---------------- LET q = "SELECT a.num_doc, CONVERT(VARCHAR(10),a.fecha,103), a.departamento, ISNULL(d.nom_dpto,''),", " ISNULL(a.cod_mov,0), ISNULL(m.descrip_mov,''), ISNULL(m.estado_mov,0),", " ISNULL(a.cod_prod,0), ISNULL(p.descripcion,''), ISNULL(p.unidad_med,''),", " ISNULL(a.cantidad,0), ISNULL(a.medida,0),", " ISNULL(a.emp_no,0), ISNULL(RTRIM(e.nom1_emp)+' '+RTRIM(e.apell1_emp),''),", " ISNULL(a.depto_receptor,0), ISNULL(dr.nom_dpto,''), ISNULL(a.ref,0),", " ISNULL(a.status_t,'')", " FROM cttb00003 a", " LEFT JOIN adtb00001 d ON d.departamento = a.departamento", " LEFT JOIN cttb00005 m ON m.cod_mov = a.cod_mov", " LEFT JOIN cttb00001 p ON p.cod_prod = a.cod_prod", " LEFT JOIN adtb00003 e ON e.num_emp = a.emp_no", " LEFT JOIN adtb00001 dr ON dr.departamento = a.depto_receptor", " WHERE a.codigo_bloque = ? AND a.status_t IS NULL", " ORDER BY a.fecha, a.num_doc, a.cod_mov" IF m_traza_prep = 0 THEN PREPARE s_traza FROM q DECLARE c_traza CURSOR FOR s_traza LET m_traza_prep = 1 END IF LET i = 1 FOREACH c_traza USING p_bloque INTO eventos[i].* LET i = i + 1 END FOREACH IF eventos.getLength() = 0 THEN CALL fgl_winmessage("Trazabilidad", "No se encontraron movimientos activos para el bloque: " || p_bloque CLIPPED, "information") RETURN END IF # ---------------- 2. Armar secciones HTML ---------------- LET sbflow = base.StringBuffer.create() LET sbtl = base.StringBuffer.create() LET ndeptos = 0 FOR i = 1 TO eventos.getLength() # -- Clasificar el movimiento (color + etiqueta) -- CASE WHEN eventos[i].cod_mov = 1 LET cls = "inicial" LET mvlabel = "Inventario inicial" WHEN eventos[i].cod_mov = 3 IF UPSHIFT(eventos[i].recep_nom) MATCHES "*ALMAC*" THEN LET cls = "almacen" ELSE LET cls = "salida" END IF LET mvlabel = "Salida" WHEN eventos[i].cod_mov = 2 AND eventos[i].ref_no > 0 LET cls = "entrada" LET mvlabel = "Recepcion" OTHERWISE LET cls = "produccion" LET mvlabel = "Produccion" END CASE # -- Texto del producto / destino -- CASE WHEN eventos[i].cod_mov = 3 LET prodtxt = "→ ", eventos[i].recep_nom CLIPPED WHEN eventos[i].cod_mov = 1 IF LENGTH(eventos[i].prod_desc CLIPPED) > 0 THEN LET prodtxt = eventos[i].prod_desc CLIPPED ELSE LET prodtxt = "Bloque recibido" END IF OTHERWISE IF LENGTH(eventos[i].prod_desc CLIPPED) > 0 THEN LET prodtxt = eventos[i].prod_desc CLIPPED ELSE LET prodtxt = "—" END IF END CASE # -- Cantidad / medida formateadas -- LET s_cant = eventos[i].cantidad USING "<<<<<<<<<&.#" LET s_med = eventos[i].medida USING "<<<<<<<<<&.##" LET qtytxt = s_cant CLIPPED, " ", eventos[i].unidad CLIPPED, " · ", s_med CLIPPED # -- Contar departamentos distintos (secuencia) -- # (OR de Genero NO hace short-circuit; se separa para no tocar eventos[0]) IF i = 1 THEN LET ndeptos = ndeptos + 1 ELSE IF eventos[i].dep_no <> eventos[i-1].dep_no THEN LET ndeptos = ndeptos + 1 END IF END IF # -- Paso del recorrido horizontal -- IF i > 1 THEN CALL sbflow.append('
' || nl) END IF LET line = '
', '
', eventos[i].dep_nom CLIPPED, ' (', eventos[i].dep_no USING "<<<<<", ')
', '', mvlabel CLIPPED, '', '
', prodtxt CLIPPED, '
', '
', qtytxt CLIPPED, '
', '
', eventos[i].fecha[1,5], '#', eventos[i].num_doc USING "<<<<<<<<", '
' CALL sbflow.append(line) CALL sbflow.append(nl) # -- Evento de la linea de tiempo -- LET line = '
', '
', eventos[i].fecha CLIPPED, '
', '
', mvlabel CLIPPED, ' · ', eventos[i].dep_nom CLIPPED, '
', '
', prodtxt CLIPPED, '
', '
' CALL sbtl.append(line) # meta: empleado / referencia IF LENGTH(eventos[i].emp_nom CLIPPED) > 0 THEN CALL sbtl.append('Empleado: ' || eventos[i].emp_nom CLIPPED || ' ') END IF IF eventos[i].cod_mov = 3 AND LENGTH(eventos[i].recep_nom CLIPPED) > 0 THEN CALL sbtl.append('Receptor: ' || eventos[i].recep_nom CLIPPED) END IF IF eventos[i].cod_mov = 2 AND eventos[i].ref_no > 0 THEN CALL sbtl.append('Ref. salida: #' || (eventos[i].ref_no USING "<<<<<<<<")) END IF LET line = '
', '
', s_cant CLIPPED, ' ', eventos[i].unidad CLIPPED, '
', '
', s_med CLIPPED, ' · #', eventos[i].num_doc USING "<<<<<<<<", '
' CALL sbtl.append(line) CALL sbtl.append(nl) END FOR # ---------------- 3. Campos del encabezado ---------------- # Descripcion del bloque: intb00018 (por codigo_bloque) -> intb00001 (descrip_esp) LET descbloque = "" SELECT MAX(i1.descrip_esp) INTO descbloque FROM intb00018 i18 LEFT JOIN intb00001 i1 ON i1.cod_n = i18.cod_n AND i1.cod_grupo = i18.cod_grupo AND i1.cod_tipo = i18.cod_tipo AND i1.cod_sec = i18.cod_sec WHERE i18.codigo_bloque = p_bloque IF descbloque IS NULL THEN LET descbloque = "" END IF LET origen = eventos[1].dep_nom CLIPPED LET s_med = eventos[1].medida USING "<<<<<<<<<&.##" LET medida_ini = s_med CLIPPED, " ", eventos[1].unidad CLIPPED LET desc_hero = "Ingreso a produccion el ", eventos[1].fecha CLIPPED, " y recorrio ", ndeptos USING "<<", " departamento(s)." # estado por el ultimo movimiento IF eventos[eventos.getLength()].cod_mov = 3 AND UPSHIFT(eventos[eventos.getLength()].recep_nom) MATCHES "*ALMAC*" THEN LET estado_hero = "Enviado a almacen" ELSE LET estado_hero = "En proceso · " || (eventos[eventos.getLength()].dep_nom CLIPPED) END IF # ---------------- 4. Aprovechamiento por unidad (desde eventos) ---------------- # Entro/Produjo = entradas (estado_mov <> 2); Salio = salidas (estado_mov = 2). # Se usa el valor absoluto de la medida y se agrupa por unidad de medida. CALL bal.clear() FOR i = 1 TO eventos.getLength() IF eventos[i].cod_prod = 0 OR LENGTH(eventos[i].unidad CLIPPED) = 0 THEN CONTINUE FOR END IF LET med_abs = eventos[i].medida IF med_abs < 0 THEN LET med_abs = med_abs * -1 END IF LET found = 0 FOR j = 1 TO bal.getLength() IF bal[j].uni = eventos[i].unidad THEN LET found = j EXIT FOR END IF END FOR IF found = 0 THEN LET found = bal.getLength() + 1 LET bal[found].uni = eventos[i].unidad LET bal[found].entro = 0 LET bal[found].salio = 0 END IF IF eventos[i].estado_mov = 2 THEN LET bal[found].salio = bal[found].salio + med_abs ELSE LET bal[found].entro = bal[found].entro + med_abs END IF END FOR LET sbbal = base.StringBuffer.create() IF bal.getLength() = 0 THEN CALL sbbal.append(' Sin datos de medida.') ELSE FOR j = 1 TO bal.getLength() LET v_balance = bal[j].entro - bal[j].salio LET v_aprov = 0 IF bal[j].entro > 0 THEN LET v_aprov = bal[j].salio / bal[j].entro * 100 END IF IF v_aprov >= 85 THEN LET aprovcls = "hi" ELSE IF v_aprov >= 50 THEN LET aprovcls = "mid" ELSE LET aprovcls = "lo" END IF END IF LET line = ' ', bal[j].uni CLIPPED, '', '', bal[j].entro USING "<<<<<<<&.##", '', '', bal[j].salio USING "<<<<<<<&.##", '', '', v_balance USING "<<<<<<<&.##", '', '', v_aprov USING "<<<&.#", '%' CALL sbbal.append(line) CALL sbbal.append(nl) END FOR END IF # ---------------- 5. Leer template, sustituir, escribir ---------------- IF NOT os.Path.exists("prtraza.html") THEN CALL fgl_winmessage("Trazabilidad", "No se encontro la plantilla 'prtraza.html' en la carpeta del programa (bin).", "stop") RETURN END IF LET sbfull = base.StringBuffer.create() LET cin = base.Channel.create() CALL cin.openFile("prtraza.html", "r") WHILE NOT cin.isEof() CALL sbfull.append(cin.readLine()) CALL sbfull.append(nl) END WHILE CALL cin.close() CALL sbfull.replace("{{FLOW}}", sbflow.toString(), 0) CALL sbfull.replace("{{TIMELINE}}", sbtl.toString(), 0) CALL sbfull.replace("{{BALANCE}}", sbbal.toString(), 0) CALL sbfull.replace("{{BLOQUE}}", p_bloque CLIPPED, 0) CALL sbfull.replace("{{DESCBLOQUE}}", descbloque, 0) CALL sbfull.replace("{{DESC}}", desc_hero, 0) CALL sbfull.replace("{{ESTADO}}", estado_hero, 0) CALL sbfull.replace("{{ORIGEN}}", origen, 0) CALL sbfull.replace("{{MEDIDA_INI}}", medida_ini, 0) CALL sbfull.replace("{{NMOV}}", eventos.getLength() USING "<<<", 0) CALL sbfull.replace("{{NDEPTOS}}", ndeptos USING "<<", 0) # ---------------- 6. Escribir, transferir al cliente y abrir ---------------- CALL presenta_html(sbfull.toString(), "trazabilidad_bloque.html") END FUNCTION # --------------------------------------------------------------------------- # Escribe el HTML en el server, lo transfiere al TEMP del cliente y lo abre # en el navegador por defecto. # --------------------------------------------------------------------------- FUNCTION presenta_html(p_full, p_nombre) DEFINE p_full, p_nombre STRING DEFINE cout base.Channel DEFINE server_file, client_dir, client_file, res STRING LET server_file = "traza_salida.html" LET cout = base.Channel.create() CALL cout.openFile(server_file, "w") CALL cout.writeLine(p_full) CALL cout.close() CALL ui.Interface.frontCall("standard", "getenv", ["TEMP"], [client_dir]) LET client_file = client_dir CLIPPED, "\\", p_nombre CLIPPED CALL fgl_putfile(server_file, client_file) CALL ui.Interface.frontCall("standard", "shellexec", [client_file], [res]) END FUNCTION # --------------------------------------------------------------------------- # Reporte por RANGO DE FECHAS: lista los bloques con movimiento en el periodo. # Recibe las fechas como texto DD/MM/YYYY (se convierten en SQL Server). # --------------------------------------------------------------------------- FUNCTION trazabilidad_rango(f1, f2) DEFINE f1, f2 STRING DEFINE bloques DYNAMIC ARRAY OF RECORD cod_bloque CHAR(25), origen CHAR(30), f_desde CHAR(10), f_hasta CHAR(10), nmov INTEGER, ndeptos INTEGER, en_almacen SMALLINT, entro DECIMAL(18,5), salio DECIMAL(18,5), descbloque CHAR(60) END RECORD DEFINE v_aprov DECIMAL(18,5) DEFINE i INT DEFINE q, line, estcls, esttxt, f1_disp, f2_disp STRING DEFINE sbrows, sbfull base.StringBuffer DEFINE cin base.Channel DEFINE nl CHAR(1) LET nl = ASCII 10 # Fechas opcionales: en blanco = sin limite (todos los bloques) IF f1 IS NULL OR LENGTH(f1 CLIPPED) = 0 THEN LET f1 = "01/01/1900" LET f1_disp = "inicio" ELSE LET f1_disp = f1 CLIPPED END IF IF f2 IS NULL OR LENGTH(f2 CLIPPED) = 0 THEN LET f2 = "31/12/2999" LET f2_disp = "hoy" ELSE LET f2_disp = f2 CLIPPED END IF LET q = "SELECT a.codigo_bloque,", " ISNULL((SELECT TOP 1 d.nom_dpto FROM cttb00003 b", " LEFT JOIN adtb00001 d ON d.departamento = b.departamento", " WHERE b.codigo_bloque = a.codigo_bloque AND b.status_t IS NULL", " ORDER BY b.fecha, b.num_doc),''),", " CONVERT(VARCHAR(10),MIN(a.fecha),103), CONVERT(VARCHAR(10),MAX(a.fecha),103),", " COUNT(*), COUNT(DISTINCT a.departamento),", " MAX(CASE WHEN a.cod_mov = 3 AND UPPER(ISNULL(dr.nom_dpto,'')) LIKE '%ALMAC%'", " THEN 1 ELSE 0 END),", " SUM(CASE WHEN ISNULL(m.estado_mov,1) = 2 THEN 0 ELSE ABS(a.medida) END),", " SUM(CASE WHEN m.estado_mov = 2 THEN ABS(a.medida) ELSE 0 END),", " ISNULL((SELECT MAX(i1.descrip_esp) FROM intb00018 i18", " LEFT JOIN intb00001 i1 ON i1.cod_n = i18.cod_n AND i1.cod_grupo = i18.cod_grupo", " AND i1.cod_tipo = i18.cod_tipo AND i1.cod_sec = i18.cod_sec", " WHERE i18.codigo_bloque = a.codigo_bloque),'')", " FROM cttb00003 a", " LEFT JOIN adtb00001 dr ON dr.departamento = a.depto_receptor", " LEFT JOIN cttb00005 m ON m.cod_mov = a.cod_mov", " WHERE a.codigo_bloque IS NOT NULL AND a.status_t IS NULL", " AND a.fecha BETWEEN CONVERT(DATE,?,103) AND CONVERT(DATE,?,103)", " GROUP BY a.codigo_bloque", " ORDER BY MIN(a.fecha), a.codigo_bloque" IF m_rango_prep = 0 THEN PREPARE s_rango FROM q DECLARE c_rango CURSOR FOR s_rango LET m_rango_prep = 1 END IF CALL bloques.clear() LET i = 1 FOREACH c_rango USING f1, f2 INTO bloques[i].* LET i = i + 1 END FOREACH # -- Armar filas de la tabla -- LET sbrows = base.StringBuffer.create() IF bloques.getLength() = 0 THEN CALL sbrows.append(' No hay bloques con movimiento en ese rango.') ELSE FOR i = 1 TO bloques.getLength() IF bloques[i].en_almacen = 1 THEN LET estcls = "alm" LET esttxt = "En almacen" ELSE LET estcls = "pro" LET esttxt = "En proceso" END IF LET v_aprov = 0 IF bloques[i].entro > 0 THEN LET v_aprov = bloques[i].salio / bloques[i].entro * 100 END IF LET line = ' ', bloques[i].cod_bloque CLIPPED, '', '', bloques[i].descbloque CLIPPED, '', '', bloques[i].origen CLIPPED, '', '', bloques[i].f_desde CLIPPED, '', '', bloques[i].f_hasta CLIPPED, '', '', bloques[i].nmov USING "<<<<", '', '', bloques[i].ndeptos USING "<<<", '', '', bloques[i].entro USING "<<<<<<<&.##", '', '', bloques[i].salio USING "<<<<<<<&.##", '', '', v_aprov USING "<<<&.#", '%', '', esttxt, '' CALL sbrows.append(line) CALL sbrows.append(nl) END FOR END IF # -- Leer template y sustituir -- IF NOT os.Path.exists("prtraza_rango.html") THEN CALL fgl_winmessage("Trazabilidad", "No se encontro la plantilla 'prtraza_rango.html' en la carpeta del programa (bin).", "stop") RETURN END IF LET sbfull = base.StringBuffer.create() LET cin = base.Channel.create() CALL cin.openFile("prtraza_rango.html", "r") WHILE NOT cin.isEof() CALL sbfull.append(cin.readLine()) CALL sbfull.append(nl) END WHILE CALL cin.close() CALL sbfull.replace("{{ROWS}}", sbrows.toString(), 0) CALL sbfull.replace("{{DESDE}}", f1_disp, 0) CALL sbfull.replace("{{HASTA}}", f2_disp, 0) CALL sbfull.replace("{{NBLOQUES}}", bloques.getLength() USING "<<<<", 0) CALL presenta_html(sbfull.toString(), "trazabilidad_rango.html") END FUNCTION