Files
MBS/PROYECTO/ipdir/ipprrp014.4gl
T
jpegueroandClaude Sonnet 5.5 d76e6d7035 Corrige ipprrp014 (Dias de Inventarios Gerente Marcas): Detallado funcional
- Registra ipfmrp014.4fd y la dependencia FUNCIONESREPORT en mbsERP.4pw
- Quita convert(char(10),...,103) de fecha_v y fecha_oc (el campo es DATE)
- Titulo de ventana corregido
- Rescata ipprrp014.4rp del respaldo PROYECTOS/bin; el encabezado decia
  IPPRRP013 (texto fijo copiado del otro diseno), corregido a IPPRRP014

Verificado: pantalla, PDF y Excel con el gerente Adolfo Santelises (prueba
con usuario temporal, no incluido en este commit). Pendiente: Resumen por
clase, ipprrp014r.4rp no existe en el repo ni en el respaldo.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 15:47:26 -04:00

760 lines
29 KiB
Plaintext

{
-------------------------------------------------------------------------------
PROGRAMA : IPPRRP014 (OPTIMIZADO)
OBJETIVO : REPORTE DIAS DE INVENTARIOS - GERENTE DE MARCAS
PROGRAMADOR : Ing. Juan Francisco Soto
FECHA REALIZACION : Agosto 19, 1992
FECHA ULT. MODIFICA : Julio 07, 2026
DESCRIPCION : OPTIMIZACION DE RENDIMIENTO:
1. Ultima venta, ultima compra y cantidad comprada se
calculan en UN SOLO query (LEFT JOIN a subconsultas
agregadas) en lugar de 3 SELECTs por producto dentro
del REPORT. Se elimino la tautologia
d.numero_orden = d.numero_orden.
2. Se elimino DISPLAY por fila y el refresh de progreso
se hace cada 50 filas (cada refresh es un round-trip
al GDC).
3. Se elimino el SELECT a companias dentro de
BEFORE GROUP OF (ya esta cargado en MAIN).
4. CORRECCION FORMULA ROTACION:
Antes: rotacion = existencia / consumo (cobertura)
Ahora: rotacion = consumo / existencia
(indice de rotacion estandar: veces que el
inventario rota en el periodo). Se agrega ademas
la rotacion anualizada = rotacion * (365/dias).
DIAS INV se mantiene igual (formula correcta).
AGOSTO 20, 2026 - MARGEN + DETALLADO/RESUMEN:
5. MARGEN DE BENEFICIO: se calcula el precio de venta REAL
promedio del periodo (ponderado por cantidad, neto del
descuento de cabecera) desde vetb00002/vetb00003, y el
COSTO estandar (iptb00004, antes quedaba en 0). Margen %
sobre precio = (precio-costo)/precio*100. Clasificacion
por cuadrante margen x rotacion (ESTRELLA/VOLUMEN/NICHO/
REVISAR/SIN COSTO), umbrales g_margen_min / g_rot_min.
6. El formulario permite elegir DETALLADO (una linea por
producto -> ipprrp014.4rp) o RESUMEN por CLASE cod_n
(subtotales -> ipprrp014r.4rp). La agregacion por clase
se hace en el FOREACH (emite_clase) y el reporte
'resumen' es PLANO (una fila por clase, total al pie).
NOTA: el consumo del 014 es de PRODUCCION
(pcantidadConduce); el margen ponderado de la clase pesa
por ese consumo.
-------------------------------------------------------------------------------
}
GLOBALS "ipprgb000.4gl"
DEFINE sale CHAR(1)
DEFINE p_ano CHAR(4)
DEFINE fecha2 DATE
DEFINE
dias1 SMALLINT,
m_meses SMALLINT,
conmes, valor1 DECIMAL(12, 2),
representa VARCHAR(100),
num_representa INT,
chexistencia CHAR(2),
progreso, cuenta_reg, cuenta INT
# Umbrales de clasificacion margen/rotacion (ajustables aqui)
DEFINE g_margen_min DECIMAL(6,2)
DEFINE g_rot_min DECIMAL(6,2)
# Acumulador por CLASE para el reporte Resumen (una fila por cod_n),
# a nivel de modulo para que emite_clase() y la funcion principal lo compartan.
DEFINE rc RECORD
cod_n INTEGER,
nombre_clase CHAR(30),
categoria CHAR(30),
c_prod INTEGER,
c_estrella INTEGER,
c_revisar INTEGER,
c_valor_inv DECIMAL(16,2),
c_ventas DECIMAL(16,2),
c_ganancia DECIMAL(16,2),
c_margen DECIMAL(12,2),
c_rot_prom DECIMAL(12,4)
END RECORD
DEFINE rc_rot_sum DECIMAL(16,4)
DEFINE prev_cod_n INTEGER
DEFINE cbtipo ui.ComboBox
MAIN
DEFER INTERRUPT
CALL ARG_VAL(1) RETURNING usuarios
CALL ARG_VAL(2) RETURNING clave
CONNECT TO "smarmotech" USER usuarios USING clave
SELECT a.* INTO p_companias.* FROM companias a
CALL ipprrp013()
END MAIN
FUNCTION ipprrp013()
# Variable que toma el valor de los dias que se va a valorar el reporte
DEFINE c_dias INTEGER
# Variables varias para el control de busqueda de las informaciones
DEFINE selec1, select_bal STRING
# Datos precalculados por SQL (antes se buscaban con 3 SELECTs por fila)
DEFINE
w_fecha_v DATE,
w_fecha_oc DATE,
w_tcantidad DEC(12, 4)
DEFINE rotacion_anual DEC(12, 2)
# Record para la impresion del reporte
DEFINE estadis RECORD
cod_n LIKE intb00001.cod_n,
cod_grupo LIKE intb00001.cod_grupo,
cod_tipo LIKE intb00001.cod_tipo,
cod_sec LIKE intb00001.cod_sec,
descrip_esp LIKE intb00001.descrip_esp,
unidad_med LIKE iptb00002.unidad_med,
consumo DEC(12, 4),
grupo CHAR(30),
tipo CHAR(30),
codCategoria INT,
categoria CHAR(30),
fecha_v, fecha_oc DATE,
tcantidad DEC(12, 4),
existe DEC(12, 4),
valor DECIMAL(12, 4),
costo DECIMAL(12, 4),
dias INTEGER,
pcodigo CHAR(25),
cantidad_orden,
pcantidadConduce,
transito,
existencia,
reservado,
disponible,
precio,
rotacion
DEC(12, 2),
# --- Analisis margen (Agosto 2026) ---
rotacion_anual DEC(12,4),
margen DEC(12,2),
valor_inv DEC(14,2),
ventas_valor DEC(14,2),
ganancia DEC(14,2),
clasificacion CHAR(12)
END RECORD
DEFINE
handler om.SaxDocumentHandler,
r_filename STRING,
r_output STRING,
preview INTEGER
OPTIONS FORM LINE 8, ERROR LINE 23, COMMENT LINE 21
OPEN FORM ipfmrp014 FROM "ipfmrp014"
DISPLAY FORM ipfmrp014
CALL ui.Window.getCurrent().setText("Dias de Inventarios Gerente Marcas")
DISPLAY "ipprrp014" AT 4, 3 ATTRIBUTE(RED)
DISPLAY "Dias de Inventarios Gerente Marcas" AT 6, 33 ATTRIBUTE(BLACK)
INPUT BY NAME fecha1, fecha2, c_dias, m_meses, chexistencia, tipo_reporte
BEFORE INPUT
LET progreso = 10
LET c_dias = 0
# Umbrales de clasificacion margen/rotacion
LET g_margen_min = 25.00
LET g_rot_min = 2.00
# Combo Detallado / Resumen (llenado por codigo)
LET cbtipo = ui.ComboBox.forName("formonly.tipo_reporte")
IF cbtipo IS NOT NULL THEN
CALL cbtipo.clear()
CALL cbtipo.addItem("D","Detallado (por producto)")
CALL cbtipo.addItem("R","Resumen (por clase)")
END IF
IF tipo_reporte IS NULL OR tipo_reporte = " " THEN
LET tipo_reporte = "D"
END IF
DISPLAY BY NAME c_dias, progreso, tipo_reporte
SELECT DISTINCT RTRIM(a.nom1_emp) + ' ' + RTRIM(a.apell1_emp),
a.num_emp
INTO representa, num_representa
FROM adtb00003 a, seg0001 b
WHERE a.num_emp = b.num_emp
AND b.usuario = usuarios
AND a.gerentemarcas = 'SI'
IF STATUS = NOTFOUND THEN
CALL fgl_winmessage(
"INFO",
"USTED NO ES GERENTE DE MARCAS, NO PUEDE GENERAR ESTE INFORME",
"INFO")
EXIT PROGRAM
END IF
DISPLAY BY NAME representa
AFTER FIELD c_dias
IF c_dias IS NULL THEN
LET numero_msg = 16
CALL msg(numero_msg)
NEXT FIELD c_dias
END IF
AFTER FIELD fecha1
IF fecha1 IS NULL THEN
LET numero_msg = 16
CALL msg(numero_msg)
NEXT FIELD fecha1
END IF
AFTER FIELD fecha2
IF fecha2 IS NULL THEN
LET numero_msg = 16
CALL msg(numero_msg)
NEXT FIELD fecha2
END IF
AFTER FIELD m_meses
IF m_meses IS NULL OR m_meses = 0 THEN
LET numero_msg = 16
CALL msg(numero_msg)
NEXT FIELD m_meses
END IF
AFTER FIELD tipo_reporte
IF tipo_reporte IS NULL OR tipo_reporte = " " THEN
LET tipo_reporte = "D"
DISPLAY BY NAME tipo_reporte
END IF
AFTER INPUT
IF int_flag THEN
LET numero_msg = 2
CALL msg(numero_msg)
LET int_flag = FALSE
RETURN
END IF
IF chexistencia IS NULL THEN
NEXT FIELD chexistencia
END IF
END INPUT
LET dias1 = fecha2 - fecha1
LET dias1 = dias1 + 1
DISPLAY BY NAME dias1
SELECT MAX(a.ano) INTO p_ano FROM prdtable a WHERE a.fecha_corte <= fecha2
CONSTRUCT criterio
ON x.cod_categoria, a.cod_n, a.cod_grupo, a.cod_tipo, a.cod_sec
FROM cod_cat, cod_n, cod_grupo, cod_tipo, cod_sec
BEFORE CONSTRUCT
CALL categorias()
AFTER CONSTRUCT
IF int_flag THEN
LET numero_msg = 2
CALL msg(numero_msg)
LET int_flag = FALSE
RETURN
END IF
EXIT CONSTRUCT
END CONSTRUCT
# Conteo para la barra de progreso (sin ORDER BY, innecesario en un COUNT)
LET select_bal =
"SELECT COUNT(*)
FROM iptb00002 a INNER JOIN
iptb00035 x1 ON a.cod_n = x1.cod_n INNER JOIN
iptb00029 d ON a.cod_grupo = d.cod_grupo INNER JOIN
iptb00034 x ON x.cod_categoria = x1.cod_cat where ",
criterio CLIPPED,
" and a.num_emp = ",
num_representa
PREPARE comando1 FROM select_bal
EXECUTE comando1 INTO cuenta_reg
# ==============================================================================
# QUERY PRINCIPAL OPTIMIZADO (CONJUNTO, NO FILA POR FILA)
# ------------------------------------------------------------------------------
# Las subconsultas uv (ultima venta) y uc (ultima compra + cantidad) se agregan
# UNA sola vez por GROUP BY y se unen por LEFT JOIN. SQL Server recorre las
# tablas de historico una vez en total, en lugar de una vez por cada producto.
# Se corrigio la condicion muerta "d.numero_orden = d.numero_orden".
# ==============================================================================
LET select_bal =
"SELECT a.cod_n, a.cod_grupo, a.cod_tipo, a.cod_sec, a.descrip_esp,",
" a.unidad_med, '0', d.descripcion, ' ', x.cod_categoria, x.descripcion,",
" uv.fecha_v,",
" uc.fecha_oc,",
" ISNULL(uc.tcantidad, 0)",
" FROM iptb00002 a",
" INNER JOIN iptb00035 x1 ON a.cod_n = x1.cod_n",
" INNER JOIN iptb00029 d ON a.cod_grupo = d.cod_grupo",
" INNER JOIN iptb00034 x ON x.cod_categoria = x1.cod_cat",
" LEFT JOIN (",
" SELECT dv.cod_n, dv.cod_grupo, dv.cod_tipo, dv.cod_sec,",
" MAX(fv.fecha_factura) AS fecha_v",
" FROM vetb00003 dv",
" INNER JOIN vetb00002 fv ON dv.factura = fv.factura",
" WHERE fv.status_t IS NULL",
" GROUP BY dv.cod_n, dv.cod_grupo, dv.cod_tipo, dv.cod_sec",
" ) uv ON uv.cod_n = a.cod_n AND uv.cod_grupo = a.cod_grupo",
" AND uv.cod_tipo = a.cod_tipo AND uv.cod_sec = a.cod_sec",
" LEFT JOIN (",
" SELECT dc.cod_n, dc.cod_grupo, dc.cod_tipo, dc.cod_sec,",
" MAX(oc.fech_oc) AS fecha_oc,",
" SUM(dc.cantidad) AS tcantidad",
" FROM cotb00015 dc",
" INNER JOIN cotb00014 oc ON dc.num_oc = oc.num_oc",
" WHERE dc.status_t IS NULL",
" GROUP BY dc.cod_n, dc.cod_grupo, dc.cod_tipo, dc.cod_sec",
" ) uc ON uc.cod_n = a.cod_n AND uc.cod_grupo = a.cod_grupo",
" AND uc.cod_tipo = a.cod_tipo AND uc.cod_sec = a.cod_sec",
" WHERE ",
criterio CLIPPED,
" AND a.num_emp = ",
num_representa,
" ORDER BY x.cod_categoria, a.cod_n, a.cod_grupo, a.cod_tipo, a.cod_sec"
PREPARE comando2 FROM select_bal
DECLARE accion CURSOR FOR comando2
# Detallado (D) -> ipprrp014.4rp ; Resumen por clase (R) -> ipprrp014r.4rp
IF tipo_reporte = "R" THEN
LET r_filename = "ipprrp014r.4rp"
ELSE
LET r_filename = "ipprrp014.4rp"
END IF
LET idx = 1
LET prev_cod_n = -999999
DISPLAY progreso TO progreso
CALL ui.Interface.refresh()
FOREACH accion INTO estadis.*
LET estadis.pcodigo =
estadis.cod_n USING "&&&&",
"-",
estadis.cod_grupo USING "&&&&",
"-",
estadis.cod_tipo USING "&&&&",
"-",
estadis.cod_sec USING "&&&&&&"
IF int_flag THEN
LET numero_msg = 2
CALL msg(numero_msg)
LET int_flag = FALSE
LET estadis.consumo = 0
LET estadis.existe = 0
LET sale = "N"
RETURN
END IF
IF idx = 1 THEN
IF fgl_report_loadCurrentSettings(r_filename) THEN
CALL seleccionarSalida() RETURNING r_output
LET preview = 1
CALL fgl_report_selectDevice(r_output)
CALL fgl_report_selectPreview(preview)
CALL fgl_report_configurexlsxdevice(null,null,null,false,false,null,1)
LET handler = fgl_report_commitCurrentSettings()
ELSE
EXIT PROGRAM
END IF
IF handler IS NOT NULL THEN
IF tipo_reporte = "R" THEN
START REPORT resumen TO XML HANDLER handler
ELSE
START REPORT estadistica TO XML HANDLER handler
END IF
ELSE
EXIT FOREACH
END IF
END IF
# Busqueda de existencias / consumo (pendiente de convertir a conjunto,
# ver nota al final del programa)
CALL disponibles(
estadis.cod_n,
estadis.cod_grupo,
estadis.cod_tipo,
estadis.cod_sec,
fecha2)
RETURNING estadis.cantidad_orden,
estadis.pcantidadConduce,
estadis.transito,
estadis.existencia,
estadis.reservado,
valororden
IF chexistencia = 'NO' AND estadis.existencia <= 0 THEN
LET idx = idx + 1
CONTINUE FOREACH
END IF
LET estadis.consumo = estadis.pcantidadConduce
IF estadis.consumo < 0 THEN
LET estadis.consumo = estadis.consumo * -1
END IF
LET estadis.existe = estadis.existencia
# ---- COSTO ESTANDAR (iptb00004). Antes quedaba en 0; ahora se calcula
# para poder obtener el margen y el valor de exceso reales. ----
LET estadis.costo = 0
SELECT AVG(a.material+a.labor+a.gastos) INTO estadis.costo FROM iptb00004 a
WHERE a.ano = YEAR(TODAY) AND a.mes_fin = 12
AND a.cod_n = estadis.cod_n AND a.cod_grupo = estadis.cod_grupo
AND a.cod_tipo = estadis.cod_tipo AND a.cod_sec = estadis.cod_sec
AND a.status_t IS NULL
IF estadis.costo IS NULL THEN LET estadis.costo = 0 END IF
# ---- PRECIO de venta REAL promedio del periodo (ponderado por cantidad,
# neto del descuento de cabecera). Fuente vetb00002 + vetb00003. ----
LET estadis.precio = 0
SELECT SUM(d.cantidad * d.precio * (1 - (ISNULL(h.porc_desc,0)/100.0)))
/ NULLIF(SUM(d.cantidad),0)
INTO estadis.precio
FROM vetb00003 d, vetb00002 h
WHERE d.factura = h.factura
AND d.cod_n = estadis.cod_n AND d.cod_grupo = estadis.cod_grupo
AND d.cod_tipo = estadis.cod_tipo AND d.cod_sec = estadis.cod_sec
AND h.status_t IS NULL AND d.status_t IS NULL
AND h.fecha_factura BETWEEN fecha1 AND fecha2
IF estadis.precio IS NULL THEN LET estadis.precio = 0 END IF
# Calculo para los dias de produccion
IF estadis.existe > 0 THEN
LET estadis.cantidad_orden =
estadis.cantidad_orden - estadis.pcantidadConduce
LET estadis.disponible =
estadis.existe - estadis.cantidad_orden - estadis.reservado
IF estadis.consumo > 0 THEN
LET estadis.dias = (estadis.existe / estadis.consumo) * dias1
# indice de rotacion = consumo / existencia (veces en el periodo)
LET estadis.rotacion = estadis.consumo / estadis.existe
# anualizada: 365.0 (decimal) evita la division entera
LET estadis.rotacion_anual = estadis.rotacion * (365.0 / dias1)
ELSE
LET estadis.rotacion = 0
LET estadis.rotacion_anual = 0
END IF
ELSE
LET estadis.dias = 0
LET estadis.rotacion = 0
LET estadis.rotacion_anual = 0
END IF
LET rotacion_anual = estadis.rotacion_anual
LET estadis.valor = 0
IF estadis.dias >= c_dias THEN
IF estadis.dias > 0 THEN
LET valor1 = (estadis.existe * c_dias) / estadis.dias
LET estadis.valor = (estadis.existe - valor1) * estadis.costo
ELSE
LET estadis.valor = 0
END IF
END IF
# ---- Margen % sobre precio, valores y clasificacion por cuadrante ----
IF estadis.precio > 0 AND estadis.costo > 0 THEN
LET estadis.margen = ((estadis.precio - estadis.costo) / estadis.precio) * 100
ELSE
LET estadis.margen = 0
END IF
LET estadis.valor_inv = estadis.existe * estadis.costo
LET estadis.ventas_valor = estadis.consumo * estadis.precio
LET estadis.ganancia = estadis.consumo * (estadis.precio - estadis.costo)
CASE
WHEN estadis.costo <= 0
LET estadis.clasificacion = "SIN COSTO"
WHEN estadis.margen >= g_margen_min AND estadis.rotacion_anual >= g_rot_min
LET estadis.clasificacion = "ESTRELLA"
WHEN estadis.margen < g_margen_min AND estadis.rotacion_anual >= g_rot_min
LET estadis.clasificacion = "VOLUMEN"
WHEN estadis.margen >= g_margen_min AND estadis.rotacion_anual < g_rot_min
LET estadis.clasificacion = "NICHO"
OTHERWISE
LET estadis.clasificacion = "REVISAR"
END CASE
IF tipo_reporte = "R" THEN
# Agregacion por CLASE: al cambiar cod_n, emite la clase anterior
IF prev_cod_n <> estadis.cod_n THEN
IF prev_cod_n <> -999999 THEN
CALL emite_clase()
END IF
LET prev_cod_n = estadis.cod_n
LET rc.cod_n = estadis.cod_n
LET rc.categoria = estadis.categoria
LET rc.nombre_clase = NULL
SELECT a.producto INTO rc.nombre_clase
FROM iptb00020 a WHERE a.cod_n = estadis.cod_n
IF rc.nombre_clase IS NULL THEN LET rc.nombre_clase = " " END IF
LET rc.c_prod = 0
LET rc.c_estrella = 0
LET rc.c_revisar = 0
LET rc.c_valor_inv = 0
LET rc.c_ventas = 0
LET rc.c_ganancia = 0
LET rc_rot_sum = 0
END IF
LET rc.c_prod = rc.c_prod + 1
LET rc.c_valor_inv = rc.c_valor_inv + estadis.valor_inv
LET rc.c_ventas = rc.c_ventas + estadis.ventas_valor
LET rc.c_ganancia = rc.c_ganancia + estadis.ganancia
LET rc_rot_sum = rc_rot_sum + estadis.rotacion_anual
IF estadis.clasificacion = "REVISAR" THEN LET rc.c_revisar = rc.c_revisar + 1 END IF
IF estadis.clasificacion = "ESTRELLA" THEN LET rc.c_estrella = rc.c_estrella + 1 END IF
ELSE
OUTPUT TO REPORT estadistica(estadis.*, dias1, c_dias, estadis.rotacion_anual)
END IF
LET idx = idx + 1
LET estadis.consumo = 0
LET estadis.existe = 0
LET estadis.dias = 0
LET estadis.rotacion = 0
LET estadis.rotacion_anual = 0
LET estadis.precio = 0
LET estadis.margen = 0
# Refrescar progreso solo cada 50 filas: cada refresh es un
# round-trip al GDC y hacerlo por fila cuesta minutos.
IF idx MOD 50 = 0 THEN
LET progreso = (idx / cuenta_reg) * 100
DISPLAY progreso TO progreso
CALL ui.Interface.refresh()
END IF
END FOREACH
IF idx = 1 THEN
CALL fgl_winmessage(
"INFO", "NO EXISTEN INFORMACIONES CON ESTOS PARAMETROS", "INFO")
ELSE
# Emite la ultima clase acumulada (modo Resumen)
IF tipo_reporte = "R" AND prev_cod_n <> -999999 THEN
CALL emite_clase()
END IF
LET progreso = 100
DISPLAY progreso TO progreso
IF tipo_reporte = "R" THEN
FINISH REPORT resumen
ELSE
FINISH REPORT estadistica
END IF
END IF
END FUNCTION
# ------------------------------------------------------------------------------
# Calcula margen ponderado y rotacion promedio de la clase acumulada en 'rc' y la
# envia al reporte 'resumen'. Usada al cambiar de CLASE y al terminar el FOREACH.
# ------------------------------------------------------------------------------
FUNCTION emite_clase()
IF rc.c_ventas > 0 THEN
LET rc.c_margen = (rc.c_ganancia / rc.c_ventas) * 100
ELSE
LET rc.c_margen = 0
END IF
IF rc.c_prod > 0 THEN
LET rc.c_rot_prom = rc_rot_sum / rc.c_prod
ELSE
LET rc.c_rot_prom = 0
END IF
OUTPUT TO REPORT resumen(rc.*)
END FUNCTION
# ==============================================================================
# REPORTE RESUMEN POR CLASE (cod_n) -- version PLANA (una fila por clase).
# La agregacion la hace el FOREACH (emite_clase); aqui solo se imprime y se
# acumula el gran total para el pie. Alimenta ipprrp014r.4rp.
# ==============================================================================
REPORT resumen(r)
DEFINE r RECORD
cod_n INTEGER,
nombre_clase CHAR(30),
categoria CHAR(30),
c_prod INTEGER,
c_estrella INTEGER,
c_revisar INTEGER,
c_valor_inv DECIMAL(16,2),
c_ventas DECIMAL(16,2),
c_ganancia DECIMAL(16,2),
c_margen DECIMAL(12,2),
c_rot_prom DECIMAL(12,4)
END RECORD
DEFINE hora CHAR(5),
fecha DATE,
g_prod INTEGER,
g_valor_inv, g_ventas, g_ganancia DECIMAL(16,2),
g_margen DECIMAL(12,2)
FORMAT
FIRST PAGE HEADER
LET hora = TIME
LET fecha = TODAY
LET lj = (120 - LENGTH(p_companias.nombre CLIPPED)) / 2
LET g_prod = 0
LET g_valor_inv = 0
LET g_ventas = 0
LET g_ganancia = 0
ON EVERY ROW
LET g_prod = g_prod + r.c_prod
LET g_valor_inv = g_valor_inv + r.c_valor_inv
LET g_ventas = g_ventas + r.c_ventas
LET g_ganancia = g_ganancia + r.c_ganancia
IF g_ventas > 0 THEN
LET g_margen = (g_ganancia / g_ventas) * 100
ELSE
LET g_margen = 0
END IF
PRINTX r.*,
g_prod, g_valor_inv, g_ventas, g_ganancia, g_margen,
p_companias.*, fecha, hora, fecha1, fecha2
END REPORT
REPORT estadistica(x, dias2, c_dias, rot_anual)
DEFINE
x RECORD
cod_n LIKE intb00001.cod_n,
cod_grupo LIKE intb00001.cod_grupo,
cod_tipo LIKE intb00001.cod_tipo,
cod_sec LIKE intb00001.cod_sec,
descrip_esp LIKE intb00001.descrip_esp,
unidad_med LIKE iptb00002.unidad_med,
consumo LIKE iptb00006.cantidad_1,
grupo CHAR(30),
tipo CHAR(30),
codCategoria INT,
categoria CHAR(30),
fecha_v, fecha_oc DATE,
tcantidad DEC(12, 4),
existe DEC(12, 4),
valor DECIMAL(12, 3),
costo DECIMAL(12, 4),
dias INTEGER,
pcodigo CHAR(25),
cantidad_orden,
pcantidadConduce,
transito,
existencia,
reservado,
disponible,
precio,
rotacion
DEC(12, 2),
# --- Analisis margen (Agosto 2026) - debe reflejar estadis ---
rotacion_anual DEC(12,4),
margen DEC(12,2),
valor_inv DEC(14,2),
ventas_valor DEC(14,2),
ganancia DEC(14,2),
clasificacion CHAR(12)
END RECORD,
tcantidad DEC(12, 4),
rot_anual DEC(12, 2)
DEFINE
c_dias SMALLINT,
dias_oc INTEGER
DEFINE m_total DECIMAL(10, 2)
DEFINE dias2 SMALLINT
DEFINE hora CHAR(5)
DEFINE
t_total, t_valor DECIMAL(12, 2),
chorden STRING,
porden INTEGER,
fecha DATE,
pcosto DEC(12, 4),
total_registros SMALLINT,
total_grupo SMALLINT,
nombre CHAR(50),
cantidad_or DEC(12, 4)
FORMAT
FIRST PAGE HEADER
LET hora = TIME
LET fecha = TODAY
LET total_registros = 0
LET lj = (120 - LENGTH(p_companias.nombre CLIPPED)) / 2
PRINTX dias1
BEFORE GROUP OF x.cod_grupo
LET total_grupo = 0
LET hora = TIME
# OPTIMIZACION: se elimino el SELECT a companias por cada grupo;
# p_companias ya se carga una sola vez en MAIN.
ON EVERY ROW
LET total_registros = total_registros + 1
LET total_grupo = total_grupo + 1
LET nombre_ant = nombre
# OPTIMIZACION: fecha_v, fecha_oc y tcantidad ya vienen
# calculadas desde el query principal (LEFT JOIN agregado).
# Antes aqui se ejecutaban 3 SELECTs por cada producto.
LET conmes = 0
IF t_total IS NULL THEN
LET t_total = 0
END IF
IF t_valor IS NULL THEN
LET t_valor = 0
END IF
LET m_total = x.existe * x.costo
LET cantidad_or = tcantidad + x.existe
IF x.consumo > 0 THEN
LET dias_oc = (cantidad_or / x.consumo) * dias1
END IF
IF m_meses > 0 THEN
LET conmes = x.consumo / m_meses
END IF
PRINTX x.cod_n,
x.cod_grupo,
x.cod_tipo,
x.cod_sec,
x.descrip_esp,
x.consumo,
x.existe,
x.dias,
x.rotacion,
rot_anual,
x.margen,
x.clasificacion,
x.unidad_med,
fecha,
hora,
fecha1,
fecha2,
x.fecha_oc,
chorden,
x.tcantidad,
p_companias.*,
pcosto,
total_registros,
total_grupo,
x.fecha_v
END REPORT
{
================================================================================
NOTA DE OPTIMIZACION PENDIENTE - disponibles()
--------------------------------------------------------------------------------
El unico trabajo por-fila que queda es CALL disponibles() (en ipprgb000.4gl),
que probablemente ejecuta varios SELECT SUM sobre las tablas de movimientos por
cada producto. Para llevar el reporte de minutos a segundos, ese calculo tambien
debe convertirse a conjunto: un solo query con GROUP BY por
cod_n/cod_grupo/cod_tipo/cod_sec (o una tabla temporal #existencias) unido por
LEFT JOIN al query principal, igual que se hizo con ultima venta/compra.
================================================================================
}