522 lines
19 KiB
Plaintext
522 lines
19 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).
|
|
-------------------------------------------------------------------------------
|
|
}
|
|
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
|
|
|
|
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)
|
|
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
|
|
BEFORE INPUT
|
|
LET progreso = 10
|
|
LET c_dias = 0
|
|
DISPLAY BY NAME c_dias, progreso
|
|
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 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
|
|
|
|
LET r_filename = "ipprrp014.4rp"
|
|
|
|
LET idx = 1
|
|
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
|
|
START REPORT estadistica TO XML HANDLER handler
|
|
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
|
|
# 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
|
|
# ==============================================================
|
|
# FORMULA CORREGIDA: indice de rotacion = consumo / existencia
|
|
# (veces que rota el inventario en el periodo).
|
|
# La formula anterior (existe/consumo) era la COBERTURA.
|
|
# Nota: idealmente usar existencia PROMEDIO del periodo:
|
|
# (existencia a fecha1 + existencia a fecha2) / 2
|
|
# ==============================================================
|
|
LET estadis.rotacion = estadis.consumo / estadis.existe
|
|
LET rotacion_anual = estadis.rotacion * (365 / dias1)
|
|
ELSE
|
|
LET estadis.rotacion = 0
|
|
LET rotacion_anual = 0
|
|
END IF
|
|
|
|
ELSE
|
|
LET estadis.dias = 0
|
|
LET estadis.rotacion = 0
|
|
LET rotacion_anual = 0
|
|
END IF
|
|
|
|
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
|
|
LET estadis.costo = 0
|
|
|
|
OUTPUT TO REPORT estadistica(estadis.*, dias1, c_dias, rotacion_anual)
|
|
|
|
LET idx = idx + 1
|
|
LET estadis.consumo = 0
|
|
LET estadis.existe = 0
|
|
LET estadis.dias = 0
|
|
LET estadis.rotacion = 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
|
|
LET progreso = 100
|
|
DISPLAY progreso TO progreso
|
|
FINISH REPORT estadistica
|
|
END IF
|
|
|
|
END FUNCTION
|
|
|
|
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)
|
|
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.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.
|
|
================================================================================
|
|
}
|