Files
MBS/PROYECTO/ipdir/ipprrp013.4gl
T
jpegueroandClaude Sonnet 5 b6085d4140 Mueve ipdir (Productos Terminados) de PROYECTOS a PROYECTO
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>
2026-09-04 09:29:01 -04:00

792 lines
30 KiB
Plaintext

{
-------------------------------------------------------------------------------
PROGRAMA : INPRRP013
OBJETIVO : REPORTE DIAS DE INVENTARIOS
PROGRAMADOR : Ing. Juan Francisco Soto
FECHA REALIZACION : Agosto 19, 1992
MODIFICADO POR : Ing. Juan Fco. Soto
FECHA ULT. MODIFICA : Agosto 20, 2026.
DESCRIPCION : Agregar Columna Costo Standard para cantidades mayores a
una cantidad de dias especificada.
AGOSTO 20, 2026 - ANALISIS DE MARGEN Y ROTACION:
1. 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 margen % sobre precio:
margen = (precio - costo) / precio * 100
El costo es el costo estandar (iptb00004), igual que
antes. Se movio el calculo del costo ANTES del calculo
del valor de exceso para que ese valor use el costo
real (antes usaba costo = 0).
2. ROTACION: igual que en ipprrp014:
rotacion = consumo / existencia
rotacion_anual = rotacion * (365 / dias del periodo)
En este reporte el consumo son las SALIDAS del periodo
(movs 30,31,55,56,40), que es la base correcta para la
rotacion de inventario.
3. CLASIFICACION por cuadrante (margen x rotacion):
ESTRELLA = alto margen + alta rotacion
VOLUMEN = bajo margen + alta rotacion
NICHO = alto margen + baja rotacion
REVISAR = bajo margen + baja rotacion (candidato
a descontinuar / renegociar costo)
SIN COSTO = producto sin costo estandar cargado
Umbrales configurables (g_margen_min, g_rot_min).
4. Se agregan valor_inv (existe*costo), ventas_valor
(consumo*precio) y ganancia (consumo*(precio-costo))
para poder totalizar el margen PONDERADO por CLASE en
el disenio .4rp (grupo por cod_n).
NOTA: el layout .4rp debe actualizarse en GST arrastrando
los nuevos campos (ver guia adjunta).
-------------------------------------------------------------------------------
}
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)
# ------------------------------------------------------------------------------
# Umbrales de clasificacion (ajustables aqui, o promover a campos del form).
# g_margen_min : margen % sobre precio a partir del cual se considera "alto"
# g_rot_min : rotacion ANUALIZADA (veces/anio) a partir de la cual es "alta"
# ------------------------------------------------------------------------------
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 ipprrp013() 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)
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
# Combo para elegir Detallado / Resumen
DEFINE cbtipo ui.ComboBox
DEFINE prev_cod_n INTEGER
# Variables varias para el control de busqueda de las informaciones
DEFINE selec1,select_bal string
# Record para el consumo de los materiales
DEFINE consume RECORD
cod_n SMALLINT,
cod_grupo SMALLINT,
cod_tipo SMALLINT,
cod_sec SMALLINT,
existe DECIMAL(14,2)
END RECORD
# Record para la impresion del reporte, estas variables se validan con el
# select que esta en la variable selec
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 LIKE iptb00006.cantidad_2,
grupo CHAR(30),
tipo CHAR(30),
categoria CHAR(30),
fecha date,
existe DEC(12,4),
valor DECIMAL(12,4),
costo DECIMAL(12,4),
dias INTEGER,
pcodigo char(25),
cantidad_orden,pcantidadConduce,transito,existencia,reservado,disponible DEC(12,2),
# --- Analisis margen / rotacion (Agosto 2026) ---
precio DEC(12,4), -- precio venta real prom. neto del periodo
margen DEC(12,2), -- % sobre precio = (precio-costo)/precio*100
rotacion DEC(12,4), -- consumo / existencia
rotacion_anual DEC(12,4), -- rotacion * (365 / dias del periodo)
valor_inv DEC(14,2), -- existencia * costo (valor inventario)
ventas_valor DEC(14,2), -- consumo * precio (venta del periodo)
ganancia DEC(14,2), -- consumo * (precio - costo)
clasificacion CHAR(12) -- ESTRELLA/VOLUMEN/NICHO/REVISAR/SIN COSTO
END RECORD
DEFINE handler om.SaxDocumentHandler, -- return value from fgl_report_commitCurrentSettings()
r_filename STRING, -- filename of Report Design Document including .4rp extension
r_output STRING, -- output format option
preview INTEGER -- TRUE/FALSE, to set preview option
# WHENEVER ERROR CONTINUE
OPTIONS
FORM LINE 8,
ERROR LINE 23,
COMMENT LINE 21
OPEN FORM ipfmrp013 FROM "ipfmrp013"
DISPLAY FORM ipfmrp013
#CALL pantalla()
DISPLAY "ipprrp013" AT 4,3 ATTRIBUTE(RED)
DISPLAY "Dias de Inventarios" AT 6,33 ATTRIBUTE(BLACK)
LET tipo_papel = 2
CALL msgrp000(tipo_papel)
# Umbrales de clasificacion margen/rotacion (ver DEFINE arriba)
LET g_margen_min = 25.00
LET g_rot_min = 2.00
INPUT BY NAME fecha1,fecha2,c_dias,m_meses,tipo_reporte
BEFORE INPUT
# Llena el combo Detallado / Resumen (mismo patron que categorias())
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
# Por defecto el reporte DETALLADO
IF tipo_reporte IS NULL OR tipo_reporte = " " THEN
LET tipo_reporte = "D"
END IF
DISPLAY BY NAME tipo_reporte
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 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
END INPUT
IF int_flag THEN
LET numero_msg = 2
CALL msg(numero_msg)
LET int_flag = false
RETURN
END IF
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
#LET p_ano = "2001"
#LET p_ano1 = "2002"
#LET p_ano = year(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
DELETE FROM tmpipprrp013
# CREACION TABLA TEMPORAL
LET select_bal =
{ "SELECT a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_Sec,a.descrip_esp,a.unidad_med,SUM(b.cantidad_2),",
"d.descripcion,f.descripcion ",
"FROM ",
"iptb00034 x INNER JOIN
iptb00035 x1 ON x.cod_categoria = x1.cod_cat RIGHT OUTER JOIN",
" iptb00002 a LEFT OUTER JOIN iptb00006 b ON a.cod_n = b.cod_n AND ",
" a.cod_grupo = b.cod_grupo AND a.cod_tipo = b.cod_tipo AND a.cod_sec = b.cod_sec ",
" AND b.cod_mov in (30,31,55,56,40) ",
" AND CONVERT(DATE,b.fecha,103) between ? AND ? ",
",iptb00029 d,iptb00024 f",
" WHERE ",
criterio CLIPPED,
" AND a.cod_grupo = d.cod_grupo and",
" a.cod_tipo = f.cod_tipo and",
" b.status_t IS NULL AND ",
" a.status_t is null ",
" GROUP BY a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_Sec,a.descrip_esp,",
" f.descripcion,d.descripcion,a.unidad_med ",
"ORDER BY a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_Sec"
106-1818
}
" SELECT a.cod_n, a.cod_grupo, a.cod_tipo, a.cod_sec, a.descrip_esp, a.unidad_med, ISNULL(SUM(b.cantidad_2),0), d.descripcion, f.descripcion,x.descripcion
FROM iptb00034 x INNER JOIN
iptb00035 x1 ON x.cod_categoria = x1.cod_cat RIGHT OUTER JOIN
iptb00002 AS a LEFT OUTER JOIN
iptb00006 AS b ON a.cod_n = b.cod_n AND a.cod_grupo = b.cod_grupo AND a.cod_tipo = b.cod_tipo AND a.cod_sec = b.cod_sec AND b.cod_mov IN (30, 31, 55, 56, 40) AND b.fecha BETWEEN
? AND ? INNER JOIN
iptb00029 AS d ON a.cod_grupo = d.cod_grupo INNER JOIN
iptb00024 AS f ON a.cod_tipo = f.cod_tipo ON x1.cod_n = a.cod_n
WHERE ", criterio CLIPPED, " and (b.status_t IS NULL) AND (a.status_t IS NULL)
GROUP BY a.cod_n, a.cod_grupo, a.cod_tipo, a.cod_sec, a.descrip_esp, f.descripcion, d.descripcion, a.unidad_med,x.descripcion
ORDER BY a.cod_n, a.cod_grupo, a.cod_tipo, a.cod_sec"
PREPARE comando2 FROM select_bal
DECLARE accion CURSOR FOR comando2
OPEN accion USING fecha1,fecha2
# Busqueda de los costos de los materiales
LET selec1 =
"SELECT max(a.mes_ini),max(a.mes_fin),a.cod_n,a.cod_grupo,a.cod_tipo,",
" a.cod_sec,SUM(a.material+a.labor+a.gastos) ",
"FROM ",
" iptb00034 x INNER JOIN iptb00035 x1 ON x.cod_categoria = x1.cod_cat RIGHT OUTER JOIN ",
"iptb00004 a ON a.cod_n = x1.cod_n ",
"WHERE ",
" a.ano = ? AND ", criterio CLIPPED,
"GROUP BY a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_sec ORDER BY a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_sec"
DISPLAY "<< Estoy Buscando Costos... Espere >>"
AT 19,14 ATTRIBUTE (REVERSE,BOLD)
PREPARE comando FROM selec1
DECLARE busca3 SCROLL CURSOR FOR comando
OPEN busca3 USING p_ano
{
LET select_bal =
"SELECT a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_sec,ISNULL(sum(a.cantidad_2),0) ",
"FROM ",
" iptb00034 x LEFT OUTER JOIN iptb00035 x1 ON x.cod_categoria = x1.cod_cat RIGHT OUTER JOIN ",
"iptb00006 a ON a.cod_n = x1.cod_n WHERE ",
" a.status_t is null and a.fecha <= ? AND ",criterio CLIPPED,
" GROUP BY a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_sec ORDER BY a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_sec"
DISPLAY "<< Estoy Buscando Balances Iniciales >>"
AT 19,14 ATTRIBUTE (REVERSE,BOLD)
PREPARE busca_balan FROM select_bal
DECLARE consu SCROLL CURSOR FOR busca_balan
OPEN consu USING fecha2
}
# Elige el disenio .4rp segun la opcion del formulario:
# D = Detallado (una linea por producto) -> ipprrp013.4rp
# R = Resumen (subtotales por CLASE cod_n) -> ipprrp013r.4rp
IF tipo_reporte = "R" THEN
LET r_filename = "ipprrp013r.4rp"
ELSE
LET r_filename = "ipprrp013.4rp"
END IF
LET idx = 1
LET prev_cod_n = -999999
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
# Busca el consumo de los materiales
LET sale = "N"
RETURN
END IF
IF idx =1 THEN
iF fgl_report_loadCurrentSettings(r_filename) THEN -- load the .4rp file
CALL seleccionarSalida() RETURNING r_output
LET preview=1
CALL fgl_report_selectDevice(r_output) -- changing default
CALL fgl_report_selectPreview(preview) -- changing default
LET handler = fgl_report_commitCurrentSettings() -- commit changes
ELSE
EXIT PROGRAM
END IF
--run the report
IF handler IS NOT NULL THEN -- report engine was configured ok
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
{LET sale = "N"
WHILE sale != "S"
FETCH consu INTO consume.*
IF status = notfound THEN
LET sale = "S"
LET STATUS = 0
EXIT WHILE
END IF
IF consume.cod_n = estadis.cod_n AND
consume.cod_grupo = estadis.cod_grupo AND
consume.cod_Tipo = estadis.cod_tipo AND
consume.cod_sec = estadis.cod_sec THEN
LET estadis.existe = consume.existe
LET sale = "S"
EXIT WHILE
END IF
END WHILE
}
# Busqueda del costo del material
# Se multiplica por -1 por que el movimiento 36 es negativo
IF estadis.consumo < 0 THEN
LET estadis.consumo = estadis.consumo * -1
END IF
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
LET estadis.existe = estadis.existencia
# ---- COSTO ESTANDAR (movido aqui para que el valor de exceso Y el margen
# usen el costo real; antes se calculaba despues y quedaba en cero) ----
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
# 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
LET estadis.dias = (estadis.existe / estadis.consumo) * dias1
ELSE
LET estadis.dias =0
END IF
IF estadis.disponible <= 0 THEN
CONTINUE FOREACH
END IF
IF estadis.dias < 0 THEN
LET estadis.dias = 0
END IF
# Calculo para el exedente de la existencia apartir de los dias de exeso
# especificado
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 DE BENEFICIO Y ROTACION
# ============================================================================
# Precio de venta REAL promedio del periodo, ponderado por cantidad y neto del
# descuento de cabecera (porc_desc). Fuente: facturas vetb00002 + detalle
# vetb00003 con fecha_factura dentro del rango [fecha1, fecha2].
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
# Margen % sobre precio = (precio - costo) / precio * 100
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
# Rotacion (misma formula que ipprrp014): consumo / existencia
# anualizada = rotacion * (365 / dias del periodo) -- 365.0 evita div. entera
IF estadis.existe > 0 AND estadis.consumo > 0 THEN
LET estadis.rotacion = estadis.consumo / estadis.existe
LET estadis.rotacion_anual = estadis.rotacion * (365.0 / dias1)
ELSE
LET estadis.rotacion = 0
LET estadis.rotacion_anual = 0
END IF
# Valores para totalizar el margen PONDERADO por CLASE en el .4rp
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)
# Clasificacion por cuadrante (margen x rotacion)
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 estadis.existe > 0 THEN
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
# Nueva clase: reinicia acumuladores
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)
END IF
END IF
LET idx = idx +1
LET estadis.consumo = 0
LET estadis.existe = 0
LET estadis.precio = 0
LET estadis.margen = 0
LET estadis.rotacion = 0
LET estadis.rotacion_anual = 0
END FOREACH
IF idx = 1 THEN
CALL fgl_winmessage("INFO","NO EXISTEN INFORMACIONES CON ESTOS PARAMETROS","INFO")
ELSE
IF tipo_reporte = "R" THEN
# Emite la ultima clase acumulada
IF prev_cod_n <> -999999 THEN
CALL emite_clase()
END IF
FINISH REPORT resumen
ELSE
FINISH REPORT estadistica
END IF
END IF
#RUN imprime
CLEAR SCREEN
END FUNCTION
# ------------------------------------------------------------------------------
# Calcula el margen ponderado y la rotacion promedio de la clase acumulada en
# 'rc' y la envia al reporte 'resumen'. Usada por ipprrp013() 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
REPORT estadistica(x,dias2,c_dias)
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),
categoria CHAR(30),
fecha date,
existe DEC(12,4),
valor DECIMAL(12,3),
costo DECIMAL(12,4),
dias INTEGER,
pcodigo char(25),
cantidad_orden,pcantidadConduce,transito,existencia,reservado,disponible DEC(12,2),
# --- Analisis margen / rotacion (Agosto 2026) - debe reflejar estadis ---
precio DEC(12,4),
margen DEC(12,2),
rotacion DEC(12,4),
rotacion_anual DEC(12,4),
valor_inv DEC(14,2),
ventas_valor DEC(14,2),
ganancia DEC(14,2),
clasificacion CHAR(12)
END RECORD,
cantidad_rec,cantidad_or,tcantidad DEC(12,4),
fecha_v,fecha_oc DATE
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),
xsuplidor VARCHAR(100)
FORMAT
FIRST PAGE HEADER
LET hora = time
LET fecha = today
LET total_registros = 0
LET tcantidad = 0
LET fecha_v = NULL
LET fecha_oc = NULL
BEFORE GROUP OF x.cod_grupo
let total_grupo = 0
BEFORE GROUP OF x.cod_tipo
LET hora = time
SELECT a.* INTO p_companias.* FROM companias a WHERE a.cod_comp = 1
LET lj = (120 - LENGTH(p_companias.nombre CLIPPED))/2
ON EVERY ROW
LET total_registros = total_registros +1
LET total_grupo = total_grupo +1
LET nombre_ant = nombre
# BUSCA ULTIMA FECHA DE COMPRA Y VENTA DEL PRODUCTO
LET fecha_v = NULL
SELECT convert(char(10),MAX(a.fecha_factura),103) INTO fecha_v
FROM vetb00003 d,vetb00002 a
WHERE d.factura = a.factura AND
d.cod_n=x.cod_n AND d.cod_grupo=x.cod_grupo AND
d.cod_tipo = x.cod_tipo AND d.cod_sec = x.cod_sec AND
a.status_t is NULL
LET fecha_oc = NULL
SELECT ISNULL(convert(char(10),MAX(a.fech_oc),103)," ") INTO fecha_oc
FROM cotb00015 d,cotb00014 a
WHERE d.num_oc = a.num_oc AND
d.cod_n=x.cod_n AND d.cod_grupo=x.cod_grupo AND
d.cod_tipo = x.cod_tipo AND d.cod_sec = x.cod_sec AND
d.status_t is NULL
LET tcantidad =0
SELECT SUM(d.cantidad) INTO tcantidad
FROM cotb00015 d,cotb00014 a
WHERE d.num_oc = a.num_oc AND
d.cod_n=x.cod_n AND d.cod_grupo=x.cod_grupo AND
d.cod_tipo = x.cod_tipo AND d.cod_sec = x.cod_sec AND
a.fech_oc = fecha_oc AND
d.status_t is NULL
LET xsuplidor =null
SELECT MAX(c.nom_sp) INTO xsuplidor
FROM cotb00015 d,cotb00014 a,cotb00001 c
WHERE d.num_oc = a.num_oc AND
d.cod_n=x.cod_n AND d.cod_grupo=x.cod_grupo AND
d.cod_tipo = x.cod_tipo AND d.cod_sec = x.cod_sec AND
a.fech_oc = fecha_oc AND
a.cod_sp = c.cod_sp AND
a.cod_sp_sec = c.cod_sp_sec AND
d.status_t is NULL
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
{ COLOQUE LA BUSQUEDA DE LA ORDEN PARA SUSTITUIR EL QUERY POR LA ULTIMA CANTIDAD COMPRADA
DECLARE busca_oc CURSOR FOR
SELECT a.num_oc,a.cantidad FROM cotb00015 a, cotb00014 b
WHERE a.num_oc = b.num_oc AND
a.cod_n = x.cod_n AND
a.cod_grupo = x.cod_grupo AND
a.cod_tipo = x.cod_tipo AND
a.cod_sec = x.cod_sec AND
b.cierre = "N" AND
b.status_T IS NULL
LET chorden = NULL
LET tcantidad = 0
FOREACH busca_oc INTO porden,cantidad_rec
IF cantidad_rec IS NULL THEN
LET cantidad_rec = 0
END IF
LET tcantidad = tcantidad + cantidad_Rec
LET chorden = porden using "<<<<<<",", ",chorden CLIPPED
END FOREACH
}
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
LET conmes = x.consumo / m_meses
PRINTX x.*,fecha,hora,fecha1,fecha2,fecha_oc,chorden,tcantidad,
p_companias.*,pcosto,total_registros,total_grupo,fecha_v
INSERT INTO tmpipprrp013 VALUES (fecha1,fecha2,x.categoria,xsuplidor,x.grupo,x.pcodigo,x.descrip_esp,x.existe,x.disponible,x.cantidad_orden,
x.dias,x.consumo,fecha_oc,x.costo,fecha_v)
END REPORT
# ==============================================================================
# REPORTE RESUMEN POR CLASE (cod_n) -- version PLANA (sin grupos)
# ------------------------------------------------------------------------------
# Recibe UN registro ya agregado por CLASE (lo arma el FOREACH principal) y emite
# una linea por clase via ON EVERY ROW. El gran total se acumula aqui y se
# muestra en el pie del disenio ipprrp013r.4rp (patron identico al footer de
# 'total_registros' del reporte de detalle, por eso no usa grupos ni triggers).
# ==============================================================================
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
SELECT a.* INTO p_companias.* FROM companias a WHERE a.cod_comp = 1
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