Files
MBS/PROYECTO/PRDIR/prtraza.4gl
T

488 lines
19 KiB
Plaintext

{
-----------------------------------------------------------------------------
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,
" &middot; ", 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(' <div class="arrow">&rarr;</div>' || nl)
END IF
LET line =
' <div class="step" style="--c:var(--', cls CLIPPED, ')">',
'<div class="dep">', eventos[i].dep_nom CLIPPED,
' <span class="depno">(', eventos[i].dep_no USING "<<<<<", ')</span></div>',
'<span class="mv">', mvlabel CLIPPED, '</span>',
'<div class="prod">', prodtxt CLIPPED, '</div>',
'<div class="qty">', qtytxt CLIPPED, '</div>',
'<div class="foot"><span>', eventos[i].fecha[1,5],
'</span><span>#', eventos[i].num_doc USING "<<<<<<<<", '</span></div></div>'
CALL sbflow.append(line)
CALL sbflow.append(nl)
# -- Evento de la linea de tiempo --
LET line =
' <div class="ev" style="--c:var(--', cls CLIPPED, ')"><div class="row">',
'<div class="date">', eventos[i].fecha CLIPPED, '</div>',
'<div class="main"><div class="mv">', mvlabel CLIPPED,
' &middot; ', eventos[i].dep_nom CLIPPED, '</div>',
'<div class="ttl">', prodtxt CLIPPED, '</div>',
'<div class="meta">'
CALL sbtl.append(line)
# meta: empleado / referencia
IF LENGTH(eventos[i].emp_nom CLIPPED) > 0 THEN
CALL sbtl.append('<span class="k">Empleado:</span> ' || 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('<span class="k">Receptor:</span> ' || eventos[i].recep_nom CLIPPED)
END IF
IF eventos[i].cod_mov = 2 AND eventos[i].ref_no > 0 THEN
CALL sbtl.append('<span class="k">Ref. salida:</span> #' || (eventos[i].ref_no USING "<<<<<<<<"))
END IF
LET line =
'</div></div>',
'<div class="amt"><div class="big">', s_cant CLIPPED, ' ', eventos[i].unidad CLIPPED, '</div>',
'<div class="doc">', s_med CLIPPED, ' &middot; #', eventos[i].num_doc USING "<<<<<<<<",
'</div></div></div></div>'
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 &middot; " || (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(' <tr><td colspan="5" style="text-align:center;color:#9AA1AA">Sin datos de medida.</td></tr>')
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 =
' <tr><td class="uni">', bal[j].uni CLIPPED, '</td>',
'<td class="num">', bal[j].entro USING "<<<<<<<&.##", '</td>',
'<td class="num">', bal[j].salio USING "<<<<<<<&.##", '</td>',
'<td class="num">', v_balance USING "<<<<<<<&.##", '</td>',
'<td class="num"><span class="aprov ', aprovcls, '">',
v_aprov USING "<<<&.#", '%</span></td></tr>'
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(' <tr><td colspan="11" class="empty">No hay bloques con movimiento en ese rango.</td></tr>')
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 =
' <tr><td class="bq">', bloques[i].cod_bloque CLIPPED, '</td>',
'<td>', bloques[i].descbloque CLIPPED, '</td>',
'<td>', bloques[i].origen CLIPPED, '</td>',
'<td>', bloques[i].f_desde CLIPPED, '</td>',
'<td>', bloques[i].f_hasta CLIPPED, '</td>',
'<td class="num">', bloques[i].nmov USING "<<<<", '</td>',
'<td class="num">', bloques[i].ndeptos USING "<<<", '</td>',
'<td class="num">', bloques[i].entro USING "<<<<<<<&.##", '</td>',
'<td class="num">', bloques[i].salio USING "<<<<<<<&.##", '</td>',
'<td class="num">', v_aprov USING "<<<&.#", '%</td>',
'<td><span class="est ', estcls, '">', esttxt, '</span></td></tr>'
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