Instruccion de Johnny: todos los directorios de fuentes de inventario deben migrarse de la carpeta temporal PROYECTOS a la carpeta real PROYECTO, ya que PROYECTOS sera eliminada. Se elimino tambien un archivo suelto sin relacion llamado "ipdir" que existia en PROYECTO (del commit inicial) y bloqueaba el nombre de la carpeta. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
759 lines
29 KiB
Plaintext
759 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
|
|
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,",
|
|
" convert(char(10), uv.fecha_v, 103),",
|
|
" convert(char(10), uc.fecha_oc, 103),",
|
|
" 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.
|
|
================================================================================
|
|
}
|