JSOTO Alta y compilacion G6 de programas CG, Cheques, Activos, Tesoreria, Repuestos e Informatica

Merge de main (ipprrp013/014/046/053) con conflictos resueltos:
- mbsERP.4pw: ipprrp014 conserva ambos 4rp y su 4fd.
- bin/ipprrp014.4rp: columnas MARGEN/CLASIF. + titulo IPPRRP014.

Programas agregados a mbsERP.4pw (53 compilan y enlazan con fgl602):
- CONTABILIDAD GENERAL, ACTIVOS FIJOS, REPUESTOS; grupos nuevos TESORERIA,
  CHEQUES e INFORMATICA (segprmt001 = seguridad2.0/segprmt0001.4gl).
- irdir copiado de 3.20 PROYECTOS/irdir a PROYECTO/irdir; helpers de
  PROYECTOS/otrodir en FUNCIONES_IR (departamento -> irdepartamento.4gl).
- Fuentes mas nuevos de 3.20: edprrp118, edprrp080, acprrp010, cgprmt022.
- FUNCIONES: GeneraEntradaDiario.4gl a la version 3.20 + costoLiquidacion.4gl.
- Ajustes G6: existe duplicado en irprgb000, fakeData de 22 campos y
  answer2 en irprmt036/020 (ApiMaintainC), selectOutputSimple en irprcs008,
  schemas de forms a smarmotech, referencia declarada en cgprmt005.
- cgprmt005 sigue sin enlazar: requiere funciones de EXTERIOR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 16:22:14 -04:00
co-authored by Claude Opus 5.5
90 changed files with 16840 additions and 143 deletions
+625 -1
View File
@@ -351,11 +351,14 @@ FUNCTION GeneraEntradaDiario(
END IF
#------------------------------------------------------------------------
# INVENTARIO PRODUCTOS TERMINADOS
# Ver GeneraEntradaInventario('P',...) mas abajo: la entrada de
# inventario necesita las N lineas del documento (cod_mov/bodega),
# que no caben en esta firma de valor unico.
#------------------------------------------------------------------------
IF tipoPrograma = 'INV PRODUCTOS' THEN
END IF
#------------------------------------------------------------------------
# INVENTARIO REPUESTOS
# INVENTARIO REPUESTOS -> ver GeneraEntradaInventario('R',...)
#------------------------------------------------------------------------
IF tipoPrograma = 'INV REPUESTOS' THEN
END IF
@@ -371,3 +374,624 @@ FUNCTION GeneraEntradaDiario(
END IF
END FUNCTION
{
FUNCION : GeneraEntradaInventario
OBJETIVO : Armar las lineas debito/credito del asiento al mayor (cgtb00004)
para UN documento de inventario (productos terminados o repuestos),
valorizado con costoLiquidacion() y con las cuentas contables
definidas por codigo de movimiento, replicando el criterio de
EDPRRP080 (terminados) y EDPRRP118 (repuestos).
PARAMETROS : usuario - usuario que crea el asiento (NOT NULL = consume secuencia)
tipoInv - 'P' productos terminados / 'R' repuestos
pnum_doc - num_doc del documento de inventario
pcod_mov - codigo de movimiento
pbodega - bodega del documento
pfecha - fecha del documento
RETORNA : generaE BOOLEAN (TRUE si armo al menos una linea),
registroEntrada (mismo shape que GeneraEntradaDiario).
NOTA : Solo toma las lineas con referenciaentrada IS NULL (idempotente:
si se llama dos veces no duplica). El llamador inserta el arreglo
en cgtb00004 (agregando numero_documento/cod_mov/bodega/
sistema_auxiliar), y sella <tabla>.referenciaentrada = ED.xxx.
DEBITO/CREDITO (por signo de cantidad_2, igual que los batch):
entrada (cantidad >= 0): inventario DEBITO / contrapartida CREDITO
salida (cantidad < 0): inventario CREDITO / contrapartida DEBITO
CUENTAS : terminados -> iptb00005.cuenta_1/2/3 (material/labor/gasto por
cod_mov) contra iptb00022.cuenta_no (por cod_cia).
repuestos -> irtb00002.cuenta (por articulo, como EDPRRP118)
contra iptb00022.cuenta_no (por cod_n).
COSTEO : produccion (es_produccion=1) -> desglose material/labor/gasto de
iptb00004. Compra/venta y repuestos -> costo unico costoLiquidacion()
aplicado a la cuenta material (cuenta_1).
}
FUNCTION GeneraEntradaInventario(usuario, tipoInv, pnum_doc, pcod_mov, pbodega, pfecha)
DEFINE
usuario STRING,
tipoInv CHAR(1),
pnum_doc, pcod_mov, pbodega INT,
pfecha DATE,
entra VARCHAR(25),
idrow INT,
qsql STRING,
es_prod SMALLINT,
status_mov CHAR(1),
cta_mat, cta_lab, cta_gas, cta_inv VARCHAR(14),
cta_mat_imp, cta_mat_uso VARCHAR(14),
dep_mat, dep_lab, dep_gas, dep_inv CHAR(1),
usarCuentaItem SMALLINT,
matStd, labStd, gasStd DEC(18, 4),
unit, v_mat, v_lab, v_gas, v_total DEC(18, 4),
detalle1 VARCHAR(100),
depto_cia, dep_uso INT,
dep_flag CHAR(1),
nc, k, j, m, encontrado INT
#--- Candidatos de la linea y acumulador por (cuenta, departamento).
#--- Convencion de 'valor': POSITIVO = DEBITO, NEGATIVO = CREDITO.
DEFINE
cand DYNAMIC ARRAY OF RECORD
cuenta_no VARCHAR(14),
valor DEC(18, 4)
END RECORD,
acum DYNAMIC ARRAY OF RECORD
cuenta_no VARCHAR(14),
departamento INT,
valor DEC(18, 4),
detalle VARCHAR(100)
END RECORD
DEFINE lin RECORD
cod_n, cod_grupo, cod_tipo, cod_sec INT,
cantidad DEC(18, 4),
departamento INT,
cod_cia INT,
descrip VARCHAR(180)
END RECORD
DEFINE
lineas DYNAMIC ARRAY OF RECORD
cod_n, cod_grupo, cod_tipo, cod_sec INT,
cantidad DEC(18, 4),
departamento INT,
cod_cia INT,
descrip VARCHAR(180)
END RECORD,
i INT
DEFINE
registroEntrada DYNAMIC ARRAY OF RECORD
referenciaEntrada VARCHAR(25),
fecha DATE,
tipo CHAR(1),
cuenta_no VARCHAR(8),
departamento INT,
detalle VARCHAR(100),
debito, credito DEC(12, 2)
END RECORD
CALL registroEntrada.clear()
LET idrow = 0
#--- Secuencia de la entrada al diario (id 25 = entrada automatica inventario)
CALL controlNumericoED(25, pfecha, usuario) RETURNING entra
#--- Cuentas contables
IF tipoInv = 'P' THEN
#--- Terminados: cuentas por codigo de movimiento (iptb00005), como EDPRRP080
LET qsql =
"SELECT ISNULL(status_mov,'E'), cuenta_1, cuenta_2, cuenta_3,
cuenta_imp_material
FROM marmotech.dbo.iptb00005 WHERE cod_mov = ?"
PREPARE s_cta FROM qsql
EXECUTE s_cta
INTO status_mov, cta_mat, cta_lab, cta_gas, cta_mat_imp
USING pcod_mov
FREE s_cta
LET usarCuentaItem = 0
ELSE
#--- Repuestos: la cuenta se toma del articulo (irtb00002.cuenta), como EDPRRP118.
#--- Compras locales confirmadas; internacionales pendientes de validar con
#--- finanzas (se asume el mismo criterio de EDPRRP118).
LET cta_mat = NULL
LET cta_lab = NULL
LET cta_gas = NULL
LET cta_mat_imp = NULL
LET usarCuentaItem = 1
END IF
#--- Preparados reutilizables dentro del ciclo
LET qsql =
"SELECT ISNULL(a.depto,'N') FROM marmotech.dbo.cgtb00001 a WHERE a.cuenta_no = ?"
PREPARE s_dep FROM qsql
LET qsql =
"SELECT a.cuenta_no, a.departamento FROM marmotech.dbo.iptb00022 a WHERE a.cod_cia = ?"
PREPARE s_inv FROM qsql
IF tipoInv = 'P' THEN
LET qsql =
"SELECT TOP 1 ISNULL(a.es_produccion,0) FROM marmotech.dbo.iptb00002 a
WHERE a.cod_n=? AND a.cod_grupo=? AND a.cod_tipo=? AND a.cod_sec=?"
PREPARE s_esp FROM qsql
LET qsql =
"SELECT ISNULL(AVG(ISNULL(a.material,0)),0), ISNULL(AVG(ISNULL(a.labor,0)),0),
ISNULL(AVG(ISNULL(a.gastos,0)),0)
FROM marmotech.dbo.iptb00004 a
WHERE a.cod_n=? AND a.cod_grupo=? AND a.cod_tipo=? AND a.cod_sec=?
AND a.ano=YEAR(?) AND a.mes_fin=12 AND a.status_t IS NULL"
PREPARE s_std FROM qsql
ELSE
LET qsql =
"SELECT a.cuenta FROM marmotech.dbo.irtb00002 a
WHERE a.cod_n=? AND a.cod_grupo=? AND a.cod_tipo=? AND a.cod_sec=?"
PREPARE s_itemcta FROM qsql
END IF
#--- Lectura de las lineas del documento (solo las no asentadas)
IF tipoInv = 'P' THEN
LET qsql =
"SELECT c.cod_n,c.cod_grupo,c.cod_tipo,c.cod_sec,SUM(c.cantidad_2),
ISNULL(c.departamento,0), MAX(e.cod_cia), MAX(e.descrip_esp)
FROM marmotech.dbo.iptb00006 c
INNER JOIN marmotech.dbo.iptb00002 e
ON c.cod_n=e.cod_n AND c.cod_grupo=e.cod_grupo
AND c.cod_tipo=e.cod_tipo AND c.cod_sec=e.cod_sec
WHERE c.num_doc=? AND c.cod_mov=? AND c.bodega=?
AND c.status_t IS NULL AND c.referenciaentrada IS NULL
GROUP BY c.cod_n,c.cod_grupo,c.cod_tipo,c.cod_sec,c.departamento
HAVING SUM(c.cantidad_2) <> 0"
ELSE
LET qsql =
"SELECT b.cod_n,b.cod_grupo,b.cod_tipo,b.cod_sec,SUM(b.cantidad_2),
ISNULL(b.sec_a,0), b.cod_n, MAX(a.descrip_esp)
FROM marmotech.dbo.irtb00006 b
INNER JOIN marmotech.dbo.irtb00002 a
ON b.cod_n=a.cod_n AND b.cod_grupo=a.cod_grupo
AND b.cod_tipo=a.cod_tipo AND b.cod_sec=a.cod_sec
WHERE b.num_doc=? AND b.cod_mov=? AND b.bodega=?
AND b.status_t IS NULL AND b.referenciaentrada IS NULL
GROUP BY b.cod_n,b.cod_grupo,b.cod_tipo,b.cod_sec,b.sec_a,b.cod_n
HAVING SUM(b.cantidad_2) <> 0"
END IF
PREPARE s_lin FROM qsql
DECLARE c_lin CURSOR FOR s_lin
#--- Cargar las lineas a un arreglo ANTES de procesar, para no mantener el
#--- cursor abierto mientras se hacen consultas anidadas (evita "connection busy")
CALL lineas.clear()
LET i = 0
FOREACH c_lin USING pnum_doc, pcod_mov, pbodega INTO lin.*
LET i = i + 1
LET lineas[i].* = lin.*
END FOREACH
FREE c_lin
FOR i = 1 TO lineas.getLength()
LET lin.* = lineas[i].*
#--- Valuacion segun tipo de producto
LET matStd = 0
LET labStd = 0
LET gasStd = 0
LET es_prod = 0
LET unit = 0
IF tipoInv = 'P' THEN
EXECUTE s_esp INTO es_prod
USING lin.cod_n, lin.cod_grupo, lin.cod_tipo, lin.cod_sec
IF es_prod IS NULL THEN LET es_prod = 0 END IF
END IF
IF tipoInv = 'P' AND es_prod = 1 THEN
#--- Produccion: desglose material/labor/gasto del estandard
EXECUTE s_std INTO matStd, labStd, gasStd
USING lin.cod_n, lin.cod_grupo, lin.cod_tipo, lin.cod_sec, pfecha
LET v_mat = matStd * lin.cantidad
LET v_lab = labStd * lin.cantidad
LET v_gas = gasStd * lin.cantidad
ELSE
#--- Compra/venta y repuestos: costo unico a la cuenta material
LET unit =
costoLiquidacion(
lin.cod_n, lin.cod_grupo, lin.cod_tipo, lin.cod_sec, pfecha, tipoInv)
LET v_mat = unit * lin.cantidad
LET v_lab = 0
LET v_gas = 0
END IF
LET v_total = v_mat + v_lab + v_gas
IF v_total = 0 THEN
CONTINUE FOR
END IF
#--- Cuenta del articulo para repuestos sin cuenta por movimiento
IF usarCuentaItem = 1 THEN
LET cta_mat = NULL
EXECUTE s_itemcta INTO cta_mat
USING lin.cod_n, lin.cod_grupo, lin.cod_tipo, lin.cod_sec
END IF
#--- Cuenta de inventario (contrapartida) y DEPARTAMENTO, por cod_cia.
#--- El departamento contable del item sale de iptb00022 segun el cod_cia
#--- que tenga en iptb00002; se aplica a TODA cuenta marcada depto='S'
#--- en cgtb00001.
LET cta_inv = NULL
LET depto_cia = NULL
EXECUTE s_inv INTO cta_inv, depto_cia USING lin.cod_cia
LET detalle1 =
'INV ', tipoInv, ' DOC ', pnum_doc USING "<<<<<<<<",
' MOV ', pcod_mov USING "<<<<", ' ', lin.descrip CLIPPED
IF LENGTH(detalle1) > 100 THEN
LET detalle1 = detalle1[1, 100]
END IF
#--- Candidatos de esta linea. Convencion: valor POSITIVO = DEBITO,
#--- NEGATIVO = CREDITO. Las contrapartidas aportan el valor invertido y
#--- el inventario el total, por lo que la entrada CUADRA POR CONSTRUCCION.
CALL cand.clear()
LET nc = 0
#--- ORIGEN DEL ARTICULO (iptb00022 via iptb00002.cod_cia):
#--- cod_cia 2 = PTOS. MAT.IMPORTADOS y 3 = PRODUCTOS IMPORTADOS llevan la
#--- cuenta de material IMPORTADO (iptb00005.cuenta_imp_material) en lugar
#--- de la cuenta_1 local. Mismo criterio que EDPRRP080. Labor y gasto NO
#--- se cambian: siguen saliendo de cuenta_2 / cuenta_3.
LET cta_mat_uso = cta_mat
IF tipoInv = 'P' THEN
IF lin.cod_cia = 2 OR lin.cod_cia = 3 THEN
IF cta_mat_imp IS NOT NULL THEN
LET cta_mat_uso = cta_mat_imp
END IF
END IF
END IF
IF cta_mat_uso IS NOT NULL AND v_mat <> 0 THEN
LET nc = nc + 1
LET cand[nc].cuenta_no = cta_mat_uso CLIPPED
LET cand[nc].valor = (0 - v_mat)
END IF
IF cta_lab IS NOT NULL AND v_lab <> 0 THEN
LET nc = nc + 1
LET cand[nc].cuenta_no = cta_lab CLIPPED
LET cand[nc].valor = (0 - v_lab)
END IF
IF cta_gas IS NOT NULL AND v_gas <> 0 THEN
LET nc = nc + 1
LET cand[nc].cuenta_no = cta_gas CLIPPED
LET cand[nc].valor = (0 - v_gas)
END IF
IF cta_inv IS NOT NULL AND v_total <> 0 THEN
LET nc = nc + 1
LET cand[nc].cuenta_no = cta_inv CLIPPED
LET cand[nc].valor = v_total
END IF
#--- Acumular por (cuenta, departamento): los items con el mismo cod_cia
#--- se suman en una sola linea; si hay items con cod_cia distinto, la
#--- misma cuenta se repite en la entrada con otro departamento y valor.
FOR k = 1 TO nc
LET dep_flag = 'N'
EXECUTE s_dep INTO dep_flag USING cand[k].cuenta_no
IF dep_flag = 'S' THEN
LET dep_uso = depto_cia
ELSE
LET dep_uso = NULL
END IF
LET encontrado = 0
FOR j = 1 TO acum.getLength()
IF acum[j].cuenta_no = cand[k].cuenta_no THEN
IF (acum[j].departamento IS NULL AND dep_uso IS NULL)
OR (acum[j].departamento = dep_uso) THEN
LET acum[j].valor = acum[j].valor + cand[k].valor
LET encontrado = 1
EXIT FOR
END IF
END IF
END FOR
IF encontrado = 0 THEN
LET m = acum.getLength() + 1
LET acum[m].cuenta_no = cand[k].cuenta_no
LET acum[m].departamento = dep_uso
LET acum[m].valor = cand[k].valor
LET acum[m].detalle = detalle1 CLIPPED
END IF
END FOR
END FOR
#--- Emitir UNA linea por cada (cuenta, departamento) acumulado
FOR j = 1 TO acum.getLength()
IF acum[j].valor = 0 THEN
CONTINUE FOR
END IF
LET idrow = idrow + 1
LET registroEntrada[idrow].referenciaEntrada = entra
LET registroEntrada[idrow].fecha = pfecha
LET registroEntrada[idrow].tipo = '1'
LET registroEntrada[idrow].cuenta_no = acum[j].cuenta_no
LET registroEntrada[idrow].departamento = acum[j].departamento
LET registroEntrada[idrow].detalle = acum[j].detalle
IF acum[j].valor > 0 THEN
LET registroEntrada[idrow].debito = acum[j].valor
LET registroEntrada[idrow].credito = 0
ELSE
LET registroEntrada[idrow].debito = 0
LET registroEntrada[idrow].credito = (0 - acum[j].valor)
END IF
END FOR
FREE s_lin
FREE s_dep
FREE s_inv
IF tipoInv = 'P' THEN
FREE s_esp
FREE s_std
ELSE
FREE s_itemcta
END IF
IF idrow = 0 THEN
RETURN FALSE, registroEntrada
END IF
RETURN TRUE, registroEntrada
END FUNCTION
{
FUNCION : registraEntradaInventario
OBJETIVO : Registrar en el mayor (cgtb00004) el asiento de UN documento de
inventario y sellar la referencia ED en la tabla de movimientos.
IMPORTANTE : Se ejecuta DENTRO de la transaccion del programa llamador.
NO hace BEGIN WORK ni COMMIT WORK. Si retorna FALSE el llamador
debe hacer ROLLBACK WORK.
PARAMETROS : usuario, tipoInv ('P'/'R'), num_doc, cod_mov, bodega, fecha
RETORNA : ok BOOLEAN, entra VARCHAR(25) con la referencia ED generada
(ok = TRUE y entra = NULL significa que no habia nada pendiente
por asentar, lo cual no es un error).
}
FUNCTION registraEntradaInventario(usuario, tipoInv, pnum_doc, pcod_mov, pbodega, pfecha)
DEFINE
usuario STRING,
tipoInv CHAR(1),
pnum_doc, pcod_mov, pbodega INT,
pfecha DATE,
genera BOOLEAN,
entra VARCHAR(25),
sistema VARCHAR(2),
tipodoc VARCHAR(10),
tabla STRING,
qsql STRING,
idx INT,
t_debito, t_credito DEC(18, 2)
DEFINE
registroEntrada DYNAMIC ARRAY OF RECORD
referenciaEntrada VARCHAR(25),
fecha DATE,
tipo CHAR(1),
cuenta_no VARCHAR(8),
departamento INT,
detalle VARCHAR(100),
debito, credito DEC(12, 2)
END RECORD
LET genera = FALSE
LET entra = NULL
CALL GeneraEntradaInventario(
usuario, tipoInv, pnum_doc, pcod_mov, pbodega, pfecha)
RETURNING genera, registroEntrada
#--- Sin lineas pendientes por asentar: no es error
IF NOT genera THEN
RETURN TRUE, entra
END IF
LET entra = registroEntrada[1].referenciaEntrada
LET tipodoc = 'INV'
#--- Guardian de cuadre (equivalente a verificaCuadre() de los batch).
#--- Si falta una cuenta configurada (iptb00022 / iptb00005 / irtb00002) la
#--- contrapartida no se genera y el asiento quedaria descuadrado: se aborta
#--- para no ensuciar el mayor.
LET t_debito = 0
LET t_credito = 0
FOR idx = 1 TO registroEntrada.getLength()
IF registroEntrada[idx].cuenta_no IS NULL THEN
CONTINUE FOR
END IF
LET t_debito = t_debito + registroEntrada[idx].debito
LET t_credito = t_credito + registroEntrada[idx].credito
END FOR
IF t_debito <> t_credito THEN
DISPLAY "ED FALLO: ASIENTO DESCUADRADO ref=", entra CLIPPED,
" debito=", t_debito, " credito=", t_credito,
" dif=", (t_debito - t_credito)
DISPLAY "ED FALLO: detalle de las lineas armadas ->"
FOR idx = 1 TO registroEntrada.getLength()
DISPLAY " cta=", registroEntrada[idx].cuenta_no,
" depto=", registroEntrada[idx].departamento,
" debito=", registroEntrada[idx].debito,
" credito=", registroEntrada[idx].credito
END FOR
DISPLAY "ED FALLO: revise que el cod_mov tenga cuenta_1/2/3 en ",
"iptb00005/irtb00005 y que el cod_cia del item tenga cuenta_no en iptb00022"
RETURN FALSE, entra
END IF
IF tipoInv = 'P' THEN
LET sistema = 'IP'
LET tabla = 'marmotech.dbo.iptb00006'
ELSE
LET sistema = 'IR'
LET tabla = 'marmotech.dbo.irtb00006'
END IF
#--- Lineas del asiento al mayor
LET qsql =
"INSERT INTO marmotech.dbo.cgtb00004
(fecha,tipo,ref,cuenta_no,departamento,detalle_1,debito,credito,
us_crea,fech_crea,numero_documento,cod_mov,bodega,
sistema_auxiliar,tipo_documento)
VALUES (?,?,?,?,?,?,?,?,?,GETDATE(),?,?,?,?,?)"
PREPARE s_insed FROM qsql
FOR idx = 1 TO registroEntrada.getLength()
IF registroEntrada[idx].cuenta_no IS NULL THEN
CONTINUE FOR
END IF
EXECUTE s_insed
USING registroEntrada[idx].fecha,
registroEntrada[idx].tipo,
registroEntrada[idx].referenciaEntrada,
registroEntrada[idx].cuenta_no,
registroEntrada[idx].departamento,
registroEntrada[idx].detalle,
registroEntrada[idx].debito,
registroEntrada[idx].credito,
usuario,
pnum_doc,
pcod_mov,
pbodega,
sistema,
tipodoc
IF STATUS < 0 THEN
DISPLAY "ED FALLO: INSERT cgtb00004 status=", STATUS,
" ", SQLERRMESSAGE
DISPLAY " ref=", entra CLIPPED,
" cta=", registroEntrada[idx].cuenta_no,
" depto=", registroEntrada[idx].departamento,
" debito=", registroEntrada[idx].debito,
" credito=", registroEntrada[idx].credito
DISPLAY " doc=", pnum_doc, " mov=", pcod_mov, " bodega=", pbodega,
" sistema=", sistema
FREE s_insed
RETURN FALSE, entra
END IF
END FOR
FREE s_insed
#--- Sellar la referencia en las lineas del documento
LET qsql =
"UPDATE ",
tabla,
" SET referenciaentrada = ?
WHERE num_doc = ? AND cod_mov = ? AND bodega = ?
AND status_t IS NULL AND referenciaentrada IS NULL"
PREPARE s_updref FROM qsql
EXECUTE s_updref USING entra, pnum_doc, pcod_mov, pbodega
IF STATUS < 0 THEN
DISPLAY "ED FALLO: UPDATE ", tabla, ".referenciaentrada status=", STATUS,
" ", SQLERRMESSAGE
DISPLAY " ref=", entra CLIPPED, " doc=", pnum_doc,
" mov=", pcod_mov, " bodega=", pbodega
FREE s_updref
RETURN FALSE, entra
END IF
FREE s_updref
RETURN TRUE, entra
END FUNCTION
{
FUNCION : anulaEntradaPorRef
OBJETIVO : Anular en el mayor (cgtb00004) las lineas de UNA entrada ED,
marcandolas status_t = 'E'. Mismo criterio que usa
ccprmt016 para anular los asientos de CxC.
IMPORTANTE : Se ejecuta DENTRO de la transaccion del programa llamador.
NO hace BEGIN WORK ni COMMIT WORK.
RETORNA : TRUE si anulo o si no habia nada que anular; FALSE si fallo.
NOTA : NO se borra la referencia de la tabla de movimientos: el
movimiento queda con status_t='E' y conserva su referencia
como rastro de cual asiento se anulo.
}
FUNCTION anulaEntradaPorRef(usuario, refED)
DEFINE
usuario STRING,
refED VARCHAR(25),
qsql STRING
IF refED IS NULL THEN
RETURN TRUE
END IF
IF LENGTH(refED) = 0 THEN
RETURN TRUE
END IF
LET qsql =
"UPDATE marmotech.dbo.cgtb00004
SET status_t = 'E', us_mod = ?, fech_mod = GETDATE()
WHERE ref = ? AND status_t IS NULL"
PREPARE s_anuED FROM qsql
EXECUTE s_anuED USING usuario, refED
IF STATUS < 0 THEN
DISPLAY "ED FALLO: no se pudo anular el asiento ", refED CLIPPED,
" status=", STATUS, " ", SQLERRMESSAGE
FREE s_anuED
RETURN FALSE
END IF
FREE s_anuED
RETURN TRUE
END FUNCTION
{
FUNCION : anulaEntradaInventario
OBJETIVO : Anular el asiento al mayor de UN documento de inventario.
Busca la referencia ED sellada en la tabla de movimientos y
delega en anulaEntradaPorRef.
PARAMETROS : usuario, tipoInv ('P'/'R'), num_doc, cod_mov,
bodega (puede venir NULL: entonces no filtra por bodega,
que es como anula irprmt066).
IMPORTANTE : Dentro de la transaccion del llamador. Si retorna FALSE el
llamador debe hacer ROLLBACK WORK.
RETORNA : ok BOOLEAN, refED de la entrada anulada (NULL si no habia)
}
FUNCTION anulaEntradaInventario(usuario, tipoInv, pnum_doc, pcod_mov, pbodega)
DEFINE
usuario STRING,
tipoInv CHAR(1),
pnum_doc, pcod_mov, pbodega INT,
refED VARCHAR(25),
tabla STRING,
qsql STRING,
ok BOOLEAN
IF tipoInv = 'P' THEN
LET tabla = 'marmotech.dbo.iptb00006'
ELSE
LET tabla = 'marmotech.dbo.irtb00006'
END IF
#--- La referencia se busca SIN filtrar status_t: al momento de llamarnos
#--- las lineas del movimiento ya pueden estar marcadas 'E'.
LET refED = NULL
LET qsql =
"SELECT MAX(a.referenciaentrada) FROM ",
tabla,
" a WHERE a.num_doc = ? AND a.cod_mov = ? AND (a.bodega = ? OR ? IS NULL)"
PREPARE s_refanu FROM qsql
EXECUTE s_refanu INTO refED USING pnum_doc, pcod_mov, pbodega, pbodega
FREE s_refanu
IF refED IS NULL THEN
#--- Documento sin asiento (anterior al automatico, o costo cero)
RETURN TRUE, refED
END IF
LET ok = anulaEntradaPorRef(usuario, refED)
RETURN ok, refED
END FUNCTION
+149
View File
@@ -0,0 +1,149 @@
{
FUNCION : costoLiquidacion
OBJETIVO : Determinar el costo unitario que debe tener un producto terminado
o un repuesto para valorizar el asiento de inventario en linea,
de acuerdo a la liquidacion de mercancia (cttb00013) o al costo
estandard. Centraliza el criterio de costeo que antes aplicaba
el usuario de forma manual.
DESARROLLADO : Equipo Tecnologia (Juan Soto)
FECHA : 14 Septiembre 2026
REGLA DE COSTO (definida por el usuario):
- Repuestos (ptipoInv = 'R'):
Siempre costo estandard irtb00013.costo_st (ano del documento, mes_fin = 12).
- Terminados (ptipoInv = 'P'):
* iptb00002.es_produccion = TRUE -> SIEMPRE costo estandard iptb00004
(material + labor + gastos, mes_fin = 12).
* es_produccion = FALSE (compra/venta):
- Se busca la ULTIMA liquidacion DEL MES del documento en cttb00013.
(Solo el mes: si la liquidacion fue en un mes anterior, lqprrp004
ya actualizo el costo estandard, por lo que ese ya lo refleja.)
- Si hay liquidacion del mes Y su cantidad liquidada > existencia actual
(SUM iptb00006.cantidad_2 WHERE status_t IS NULL) -> costo_lq.
- En caso contrario (existencia >= liquidada, o no hay liquidacion
del mes) -> costo estandard iptb00004.
NOTA: usa nombres calificados marmotech.dbo.* igual que GeneraEntradaDiario.4gl y
controlNumericoED.4gl, para leer siempre la BD correcta sin depender de la
conexion del programa llamador.
}
FUNCTION costoLiquidacion(pcod_n, pcod_grupo, pcod_tipo, pcod_sec, pfecha, ptipoInv)
DEFINE
pcod_n, pcod_grupo, pcod_tipo, pcod_sec INT,
pfecha DATE,
ptipoInv CHAR(1),
costo DEC(18, 4),
costo_std DEC(18, 4),
xcosto_lq DEC(18, 4),
cant_lq DEC(18, 4),
existencia DEC(18, 4),
es_prod SMALLINT,
qsql STRING
LET costo = 0
LET costo_std = 0
LET xcosto_lq = 0
LET cant_lq = 0
LET existencia = 0
LET es_prod = 0
#-------------------------------------------------------------------------
# REPUESTOS -> costo estandard irtb00013.costo_st
#-------------------------------------------------------------------------
IF ptipoInv = 'R' THEN
LET qsql =
"SELECT ISNULL(a.costo_st,0)
FROM marmotech.dbo.irtb00013 a
WHERE a.cod_n = ? AND a.cod_grupo = ? AND a.cod_tipo = ? AND a.cod_sec = ?
AND a.ano = YEAR(?) AND a.mes_fin = 12 AND a.status_t IS NULL"
PREPARE sc_rep FROM qsql
EXECUTE sc_rep INTO costo
USING pcod_n, pcod_grupo, pcod_tipo, pcod_sec, pfecha
FREE sc_rep
IF costo IS NULL THEN
LET costo = 0
END IF
RETURN costo
END IF
#-------------------------------------------------------------------------
# TERMINADOS -> costo estandard iptb00004 (material + labor + gastos)
# Se calcula siempre porque es el respaldo y el valor para es_produccion.
#-------------------------------------------------------------------------
LET qsql =
"SELECT ISNULL(AVG(ISNULL(a.material,0)+ISNULL(a.labor,0)+ISNULL(a.gastos,0)),0)
FROM marmotech.dbo.iptb00004 a
WHERE a.cod_n = ? AND a.cod_grupo = ? AND a.cod_tipo = ? AND a.cod_sec = ?
AND a.ano = YEAR(?) AND a.mes_fin = 12 AND a.status_t IS NULL"
PREPARE sc_std FROM qsql
EXECUTE sc_std INTO costo_std
USING pcod_n, pcod_grupo, pcod_tipo, pcod_sec, pfecha
FREE sc_std
IF costo_std IS NULL THEN
LET costo_std = 0
END IF
# es_produccion del articulo
LET qsql =
"SELECT TOP 1 ISNULL(a.es_produccion,0)
FROM marmotech.dbo.iptb00002 a
WHERE a.cod_n = ? AND a.cod_grupo = ? AND a.cod_tipo = ? AND a.cod_sec = ?"
PREPARE sc_esp FROM qsql
EXECUTE sc_esp INTO es_prod
USING pcod_n, pcod_grupo, pcod_tipo, pcod_sec
FREE sc_esp
IF es_prod IS NULL THEN
LET es_prod = 0
END IF
# Producto de produccion -> siempre estandard
IF es_prod = 1 THEN
RETURN costo_std
END IF
#-------------------------------------------------------------------------
# COMPRA/VENTA -> ultima liquidacion DEL MES vs existencia
#-------------------------------------------------------------------------
LET qsql =
"SELECT TOP 1 ISNULL(a.costo_lq,0), ISNULL(a.cantidad,0)
FROM marmotech.dbo.cttb00013 a
WHERE a.cod_n = ? AND a.cod_grupo = ? AND a.cod_tipo = ? AND a.cod_sec = ?
AND YEAR(a.fecha_lq) = YEAR(?) AND MONTH(a.fecha_lq) = MONTH(?)
AND a.fecha_lq <= ?
AND a.status_t IS NULL
ORDER BY a.fecha_lq DESC, a.fech_crea DESC"
PREPARE sc_lq FROM qsql
EXECUTE sc_lq INTO xcosto_lq, cant_lq
USING pcod_n, pcod_grupo, pcod_tipo, pcod_sec, pfecha, pfecha, pfecha
FREE sc_lq
# No hay liquidacion en el mes -> estandard
IF STATUS = NOTFOUND OR xcosto_lq IS NULL THEN
RETURN costo_std
END IF
# Existencia actual del articulo (solo movimientos vigentes)
LET qsql =
"SELECT ISNULL(SUM(a.cantidad_2),0)
FROM marmotech.dbo.iptb00006 a
WHERE a.cod_n = ? AND a.cod_grupo = ? AND a.cod_tipo = ? AND a.cod_sec = ?
AND a.status_t IS NULL"
PREPARE sc_exi FROM qsql
EXECUTE sc_exi INTO existencia
USING pcod_n, pcod_grupo, pcod_tipo, pcod_sec
FREE sc_exi
IF existencia IS NULL THEN
LET existencia = 0
END IF
# Cantidad liquidada mayor que la existencia -> costo liquidado
IF cant_lq > existencia THEN
RETURN xcosto_lq
END IF
# Existencia mayor o igual -> costo estandard
RETURN costo_std
END FUNCTION
+19
View File
@@ -0,0 +1,19 @@
FUNCTION cuentas()
DEFINE kcuenta VARCHAR(14),
kdescripcion CHAR(60),
cuentas ui.ComboBox
LET cuentas = ui.combobox.forname("formonly.xcuenta")
DECLARE busca_cuenta CURSOR FOR
SELECT RTRIM(a.cuenta_no),RTRIM(a.descripcion)+'('+RTRIM(a.cuenta_no)+')'
FROM cgtb00001 a
WHERE a.nivel > 2 AND a.cuenta_no LIKE '7%' AND a.status_t IS NULL
ORDER BY a.descripcion
CALL cuentas.clear()
FOREACH busca_cuenta INTO kcuenta,kdescripcion
CALL cuentas.additem(kcuenta,kdescripcion)
END FOREACH
END FUNCTION
+54
View File
@@ -0,0 +1,54 @@
FUNCTION busca_departamento()
DEFINE pdepartamento ui.combobox,
kdepartamento SMALLINT,kdescripcion CHAR(30)
LET pdepartamento = ui.combobox.forname("formonly.departamento")
CALL pdepartamento.clear()
DECLARE pagar CURSOR FOR
SELECT departamento, nom_dpto FROM adtb00001
WHERE status_t is null
ORDER BY nom_dpto
FOREACH pagar INTO kdepartamento,kdescripcion
CALL pdepartamento.additem(kdepartamento,kdescripcion)
END FOREACH
END FUNCTION
{
FUNCTION departamento_user()
DEFINE departamentos_user DYNAMIC ARRAY OF RECORD
departamento INT,
titulo VARCHAR(100)
END RECORD
DECLARE busca_dep CURSOR FOR
SELECT '0',a.departamento,a.nom_dpto,a.tipo_departamento
FROM adtb00001 a WHERE a.status_t IS NULL
ORDER BY a.nom_dpto
LET idx = 1
FOREACH busca_dep INTO arr_departamentos[idx].*
LET idx = idx + 1
END FOREACH
END FUNCTION
}
FUNCTION departamento_user(xuser)
DEFINE pdepartamento ui.combobox,
kdepartamento SMALLINT,kdescripcion CHAR(30),
xuser STRING
LET pdepartamento = ui.combobox.forname("formonly.departamento")
CALL pdepartamento.clear()
DECLARE depto CURSOR FOR
SELECT a.departamento, a.nom_dpto FROM adtb00001 a,adtb00056 b
WHERE a.departamento = b.departamento_visualiza AND
b.usuario = xuser AND
a.status_t is null
ORDER BY nom_dpto
FOREACH depto INTO kdepartamento,kdescripcion
CALL pdepartamento.additem(kdepartamento,kdescripcion)
END FOREACH
END FUNCTION
+42
View File
@@ -0,0 +1,42 @@
FUNCTION maquinas(xdepartamento)
DEFINE
kmaquina VARCHAR(14),
kdescripcion CHAR(75),
maquinas ui.ComboBox,
xdepartamento INT
LET maquinas = ui.combobox.forname("formonly.codigo")
IF xdepartamento = -1 THEN
DECLARE busca_maquinas1 CURSOR FOR
SELECT a.codigo,
RTRIM(a.nombre) + '(' + ISNULL(RTRIM(a.codigo_externo), ' ')
+ ')'
FROM irtb00009 a
WHERE a.status_t IS NULL AND a.codigo_externo IS NOT NULL
ORDER BY a.nombre
CALL maquinas.clear()
FOREACH busca_maquinas1 INTO kmaquina, kdescripcion
CALL maquinas.additem(kmaquina, kdescripcion)
END FOREACH
ELSE
DECLARE busca_maquinas2 CURSOR FOR
SELECT a.codigo,
RTRIM(a.nombre) + '(' + ISNULL(RTRIM(a.codigo_externo), ' ')
+ ')'
FROM irtb00009 a
WHERE a.status_t IS NULL AND a.departamento = xdepartamento
AND a.codigo_externo IS NOT NULL
ORDER BY a.nombre
CALL maquinas.clear()
FOREACH busca_maquinas2 INTO kmaquina, kdescripcion
CALL maquinas.additem(kmaquina, kdescripcion)
END FOREACH
END IF
END FUNCTION
+42
View File
@@ -0,0 +1,42 @@
{
FUNCION : msgmodal
OBJETIVO : Mostrar un mensaje de msgtable en una ventana MODAL.
Complementa a msg(), que lo despliega en la linea de ERROR.
DESARROLLADO : Equipo Tecnologia (Juan Soto)
FECHA : 21 Septiembre 2026
CUANDO USAR CUAL
----------------
msg(n) -> Errores de VALIDACION durante la captura. El usuario esta
parado en el campo, lee el aviso en la linea de ERROR,
corrige y sigue. No interrumpe el flujo.
msgmodal(n) -> Hechos que el usuario NO PUEDE pasar por alto, tipicamente
cuando se ABORTO la transaccion y el documento NO se guardo.
Si eso solo parpadeara en la linea de ERROR mientras la
pantalla se limpia, el usuario se iria creyendo que guardo.
El texto vive en msgtable igual que el de msg(), de modo que se puede
corregir sin recompilar los programas.
}
FUNCTION msgmodal(numero_msg)
DEFINE
numero_msg SMALLINT,
descripcion CHAR(120)
LET descripcion = NULL
SELECT desc_msg
INTO descripcion
FROM msgtable
WHERE cod_msg = numero_msg
IF status = NOTFOUND OR descripcion IS NULL THEN
LET descripcion = "MENSAJE NO DEFINIDO EN MSGTABLE: ",
numero_msg USING "<<<<"
END IF
CALL fgl_winmessage("AVISO", descripcion CLIPPED, "stop")
END FUNCTION
+45
View File
@@ -0,0 +1,45 @@
FUNCTION productos_incluye(tabla1,tabla2,tabla3,tabla4,tabla5,xcriterio,xbodega,xfecha,xseccion,xtramo)
DEFINE tabla1,tabla2,tabla3,tabla4,tabla5,selec,xcriterio STRING,
xbodega,xseccion,xtramo SMALLINT,
xfecha DATE,
xid INT,
xfech_crea DATETIME YEAR TO MINUTE
DEFINE xitems RECORD
cod_n SMALLINT,
cod_grupo SMALLINT,
cod_tipo SMALLINT,
cod_sec SMALLINT
END RECORD
LET xfech_crea = CURRENT
LET selec = "SELECT DISTINCT a.cod_n,a.cod_Grupo,a.cod_tipo,a.cod_sec FROM ",tabla1 CLIPPED," AS a ",
" INNER JOIN ",tabla5 CLIPPED ," 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 a.status_t IS NULL AND b.status_t is NULL AND a.control_existencia = 'SI' ",
" WHERE b.bodega = ",xbodega USING "<<<", " and ",xcriterio CLIPPED
PREPARE comando FROM selec
DECLARE busca_item CURSOR FOR comando
LET selec = "SELECT DISTINCT a.cod_n FROM ",tabla3 CLIPPED,
" AS a WHERE a.cod_n = ? AND a.cod_grupo = ? AND a.cod_tipo = ? AND a.cod_sec = ? AND a.fecha = ? AND a.bodega = ?"
PREPARE comando1 FROM selec
LET selec = "INSERT INTO ",tabla3 CLIPPED," (idtrx,fecha,bodega,cod_Seccion,cod_tramo,cod_n,cod_grupo,cod_tipo,cod_Sec,cantidad,us_crea,fech_crea) ",
" VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"
PREPARE inserta FROM selec
LET selec = "SELECT MAX(a.idtrx) FROM ",tabla3 CLIPPED, " AS a WHERE a.fecha =? AND a.bodega =? "
PREPARE busca_max FROM selec
EXECUTE busca_max INTO xid USING xfecha,xbodega
FOREACH busca_item INTO xitems.*
EXECUTE comando1 USING xitems.cod_n,xitems.cod_grupo,xitems.cod_tipo,xitems.cod_sec,xfecha,xbodega
IF STATUS = NOTFOUND THEN
EXECUTE inserta USING xid,xfecha,xbodega,xseccion,xtramo,xitems.cod_n,xitems.cod_grupo,xitems.cod_tipo,
xitems.cod_sec,"0","INCLUYE",xfech_crea
LET xid = xid + 1
END IF
END FOREACH
END FUNCTION
+17
View File
@@ -0,0 +1,17 @@
FUNCTION cseccion(almacen)
DEFINE kcod_seccion,almacen SMALLINT,
kdescripcion CHAR(30),
pseccion ui.ComboBox
LET pseccion = ui.combobox.forname("formonly.seccion")
DECLARE busca_sec CURSOR FOR
SELECT a.cod_seccion,a.descripcion FROM Intb00010 a
WHERE a.cod_bodega = almacen
CALL pseccion.clear()
FOREACH busca_sec INTO kcod_seccion,kdescripcion
CALL pseccion.additem(kcod_seccion,kdescripcion)
END FOREACH
CALL pseccion.additem(99,"TODAS")
END FUNCTION
+18
View File
@@ -0,0 +1,18 @@
FUNCTION ctramo(almacen,seccion)
DEFINE kcod_tramo,almacen,seccion SMALLINT,kdescripcion CHAR(30),
ptramo ui.ComboBox
LET ptramo = ui.combobox.forname("formonly.tramo")
DECLARE busca_tramo CURSOR FOR
SELECT a.cod_tramo,a.descripcion FROM Intb00043 a
WHERE a.cod_bodega = almacen and a.cod_Seccion = seccion
CALL ptramo.clear()
FOREACH busca_tramo INTO kcod_tramo,kdescripcion
DISPLAY "tramos ",kcod_tramo,kdescripcion," ",almacen," ",seccion
CALL ptramo.additem(kcod_tramo,kdescripcion)
END FOREACH
CALL ptramo.additem(99,"TODOS")
END FUNCTION