Files
DECOMARMOL/PROYECTO/cpdir/cpprmt001_ecf.4gl
T

818 lines
34 KiB
Plaintext

{
-----------------------------------------------------------------------------
MODULO : CPPRMT001_ECF
OBJETIVO : Compra electronica e-CF 41 (Comprobante de Compras) desde
cpprmt001 (Facturas de proveedores).
- Boton "Compra Electronica": trae el proximo e-NCF 41 del rango
(vetb00071/vetb00073, tipo_doc 'FT', codigo_dgii '41') y abre la
ventana cpfmwd041 donde se digita el NETO, el % de ISR, el tipo
de retencion ISR (606) y si lleva ITBIS. El resto se calcula solo:
subtotal = neto / (1 - %ISR)
ISR retenido = subtotal - neto
ITBIS (18%) = subtotal * 18% (si lleva ITBIS)
ITBIS retenido = 100% del ITBIS
total = subtotal + ITBIS
- El e-NCF se RESERVA al grabar (dentro de la transaccion), asi no
se pierden secuencias si cancelan.
- La retencion de ISR de la distribucion contable debe estar en una
cuenta marcada como retencion en el catalogo (cgtb00001.retencion).
- Al grabar: guarda el rastreo en cptb00041 y envia el XML del 41.
FECHA : Octubre 2026
-----------------------------------------------------------------------------
}
IMPORT util
GLOBALS "cpprgb000.4gl"
DEFINE wcp om.SaxDocumentHandler
-- estado de la compra electronica en curso
DEFINE e41 RECORD
activo BOOLEAN,
ncf41 VARCHAR(13),
nom41 VARCHAR(90),
rnc41 VARCHAR(11),
neto41 DECIMAL(12,2),
porc41 DECIMAL(5,2),
cisr41 SMALLINT,
itbis41 CHAR(1),
bs41 CHAR(1),
sub41 DECIMAL(12,2),
itb41 DECIMAL(12,2),
isr41 DECIMAL(12,2),
itbret41 DECIMAL(12,2),
total41 DECIMAL(12,2)
END RECORD
#-----------------------------------------------------------------------------
FUNCTION e41_reset()
INITIALIZE e41.* TO NULL
LET e41.activo = FALSE
END FUNCTION
FUNCTION e41_activo() RETURNS BOOLEAN
RETURN NVL(e41.activo, FALSE)
END FUNCTION
FUNCTION e41_ncf() RETURNS VARCHAR(13)
RETURN e41.ncf41
END FUNCTION
FUNCTION e41_neto() RETURNS DECIMAL(12,2)
RETURN e41.neto41
END FUNCTION
FUNCTION e41_itbis() RETURNS DECIMAL(12,2)
RETURN e41.itb41
END FUNCTION
FUNCTION e41_isr() RETURNS DECIMAL(12,2)
RETURN NVL(e41.isr41, 0)
END FUNCTION
#-----------------------------------------------------------------------------
# % de ISR permitidos: 0 = exento, 3 = servicios tecnicos,
# 15 = honorarios / profesionales / alquileres
#-----------------------------------------------------------------------------
FUNCTION porc_isr_valido41(pporc)
DEFINE pporc DECIMAL(5,2)
IF pporc IS NULL THEN
RETURN FALSE
END IF
RETURN (pporc = 0 OR pporc = 3 OR pporc = 15)
END FUNCTION
#-----------------------------------------------------------------------------
# Calcula los montos del 41 a partir del neto (lo que recibe el proveedor).
# El ISR se saca por diferencia para que el neto cuadre exacto al centavo.
#-----------------------------------------------------------------------------
FUNCTION calcula41(pneto, pporc_isr, plleva_itbis)
DEFINE pneto DECIMAL(12,2),
pporc_isr DECIMAL(5,2),
plleva_itbis CHAR(1),
xsubtotal, xitbis, xisr, xitbis_ret, xtotal DECIMAL(12,2)
LET pneto = NVL(pneto, 0)
LET pporc_isr = NVL(pporc_isr, 0)
LET xsubtotal = pneto / (1 - pporc_isr / 100) -- se redondea a 2 decimales
LET xisr = xsubtotal - pneto
IF plleva_itbis = 'S' THEN
LET xitbis = xsubtotal * 18 / 100
ELSE
LET xitbis = 0
END IF
LET xitbis_ret = xitbis -- se retiene el 100%
LET xtotal = xsubtotal + xitbis
RETURN xsubtotal, xitbis, xisr, xitbis_ret, xtotal
END FUNCTION
#-----------------------------------------------------------------------------
# Deja solo los digitos (cedula/RNC sin guiones ni espacios)
#-----------------------------------------------------------------------------
FUNCTION e41_solo_digitos(ptexto)
DEFINE ptexto, xres STRING,
i INT
LET xres = ""
FOR i = 1 TO ptexto.getLength()
IF ptexto.getCharAt(i) MATCHES "[0-9]" THEN
LET xres = xres.append(ptexto.getCharAt(i))
END IF
END FOR
IF xres.getLength() = 0 THEN
RETURN NULL
END IF
RETURN xres
END FUNCTION
#-----------------------------------------------------------------------------
# Proximo e-NCF 41 SIN reservarlo (solo para mostrarlo).
# Retorna NULL si no hay rango o se agoto.
#-----------------------------------------------------------------------------
FUNCTION e41_proximo_ncf() RETURNS VARCHAR(13)
DEFINE xinicial, xfinal, xult, xnuevo INT
SELECT MIN(a.ncf_inicial), MAX(a.ncf_final) INTO xinicial, xfinal
FROM vetb00071 a
WHERE a.tipo_doc = 'FT' AND a.codigo_dgii = '41' AND a.disponible = 'S'
IF xfinal IS NULL THEN
CALL fgl_winmessage("COMPRA ELECTRONICA",
"No hay rango disponible para el e-NCF 41 (vetb00071 tipo_doc FT, codigo 41).\nLlame a finanzas.","stop")
RETURN NULL
END IF
SELECT MAX(a.ncf) INTO xult FROM vetb00073 a
WHERE a.tipo_doc = 'FT' AND a.codigo_dgii = '41'
IF xult IS NULL OR xult < NVL(xinicial, 1) - 1 THEN
LET xult = NVL(xinicial, 1) - 1
END IF
LET xnuevo = xult + 1
IF xnuevo > xfinal THEN
CALL fgl_winmessage("COMPRA ELECTRONICA","Se agoto el rango del e-NCF 41. Llame a finanzas.","stop")
RETURN NULL
END IF
RETURN "E41" || (xnuevo USING "&&&&&&&&&&")
END FUNCTION
#-----------------------------------------------------------------------------
# Reserva el e-NCF 41 (se llama DENTRO de la transaccion de grabado).
# Si vetb00073 no tiene la fila FT/41 la crea desde ncf_inicial.
# Retorna NULL si no hay rango o se agoto.
#-----------------------------------------------------------------------------
FUNCTION e41_reserva_ncf() RETURNS VARCHAR(13)
DEFINE xinicial, xfinal, xnuevo INT
SELECT MIN(a.ncf_inicial), MAX(a.ncf_final) INTO xinicial, xfinal
FROM vetb00071 a
WHERE a.tipo_doc = 'FT' AND a.codigo_dgii = '41' AND a.disponible = 'S'
IF xfinal IS NULL THEN
CALL fgl_winmessage("COMPRA ELECTRONICA","No hay rango disponible para el e-NCF 41.","stop")
RETURN NULL
END IF
UPDATE vetb00073 SET ncf = ncf + 1, us_crea = usuarios, fech_crea = getdate()
WHERE tipo_doc = 'FT' AND codigo_dgii = '41'
IF STATUS < 0 THEN
CALL fgl_winmessage("COMPRA ELECTRONICA","Error reservando el e-NCF 41: "||SQLCA.sqlcode,"stop")
RETURN NULL
END IF
IF SQLCA.sqlerrd[3] = 0 THEN
LET xnuevo = NVL(xinicial, 1)
INSERT INTO vetb00073 (codigo_dgii, tipo_doc, ncf, sucid, us_crea, fech_crea)
VALUES ('41', 'FT', xnuevo, 1, usuarios, getdate())
IF STATUS < 0 THEN
CALL fgl_winmessage("COMPRA ELECTRONICA","Error creando el contador del e-NCF 41: "||SQLCA.sqlcode,"stop")
RETURN NULL
END IF
ELSE
SELECT MAX(a.ncf) INTO xnuevo FROM vetb00073 a
WHERE a.tipo_doc = 'FT' AND a.codigo_dgii = '41'
END IF
IF xnuevo > xfinal THEN
CALL fgl_winmessage("COMPRA ELECTRONICA","Se agoto el rango del e-NCF 41. Llame a finanzas.","stop")
RETURN NULL
END IF
LET e41.ncf41 = "E41" || (xnuevo USING "&&&&&&&&&&")
RETURN e41.ncf41
END FUNCTION
#-----------------------------------------------------------------------------
# TRUE si la cuenta esta marcada como retencion en el catalogo.
# Si tiene codigo de ISR y en la ventana no se escogio, se toma de la cuenta.
#-----------------------------------------------------------------------------
FUNCTION e41_cuenta_isr(pcuenta)
DEFINE pcuenta CHAR(8),
xret CHAR(2),
xcod SMALLINT
LET xret = NULL
LET xcod = NULL
SELECT a.retencion, a.codigo_isr INTO xret, xcod
FROM cgtb00001 a
WHERE a.cuenta_no = pcuenta
IF xret IS NULL OR UPSHIFT(xret) <> "SI" THEN
RETURN FALSE
END IF
IF e41.cisr41 IS NULL AND xcod IS NOT NULL THEN
LET e41.cisr41 = xcod
END IF
RETURN TRUE
END FUNCTION
#-----------------------------------------------------------------------------
# Llena el combo de tipos de retencion ISR (cgtb00077) de la ventana del 41
#-----------------------------------------------------------------------------
FUNCTION e41_carga_cisr()
DEFINE cb ui.ComboBox,
xcod SMALLINT,
xdes CHAR(50)
LET cb = ui.ComboBox.forName("formonly.cisr41")
IF cb IS NULL THEN
RETURN
END IF
CALL cb.clear()
DECLARE c_cisr41 CURSOR FOR
SELECT a.codigo_isr, a.descripcion FROM cgtb00077 a ORDER BY a.codigo_isr
FOREACH c_cisr41 INTO xcod, xdes
CALL cb.addItem(xcod, (xcod USING "<&") || " - " || xdes CLIPPED)
END FOREACH
END FUNCTION
#-----------------------------------------------------------------------------
FUNCTION e41_recalcula()
CALL calcula41(e41.neto41, e41.porc41, e41.itbis41)
RETURNING e41.sub41, e41.itb41, e41.isr41, e41.itbret41, e41.total41
DISPLAY e41.sub41, e41.itb41, e41.isr41, e41.itbret41, e41.total41
TO sub41, itb41, isr41, itbret41, total41
END FUNCTION
#-----------------------------------------------------------------------------
# Boton "Compra Electronica": valida el suplidor, muestra el proximo e-NCF
# y captura neto / % ISR / tipo ISR / ITBIS. Retorna TRUE si se acepto.
#-----------------------------------------------------------------------------
FUNCTION e41_captura(pcod_sp, pcod_sp_sec)
DEFINE pcod_sp, pcod_sp_sec SMALLINT,
xnom VARCHAR(90),
xrnc STRING,
xproximo VARCHAR(13),
xcodigo SMALLINT
-- tipo fiscal 41 en la lista de tipos NCF
LET xcodigo = NULL
SELECT a.codigo_fiscal INTO xcodigo FROM cgtb00074 a WHERE a.codigo_fiscal = 41
IF xcodigo IS NULL THEN
CALL fgl_winmessage("COMPRA ELECTRONICA",
"No existe el tipo NCF 41 en cgtb00074. Agreguelo antes de emitir.","stop")
RETURN FALSE
END IF
-- el proveedor va en el bloque Comprador: cedula (11) o RNC (9) obligatorio
SELECT a.nom_sp, a.rnc INTO xnom, xrnc FROM cotb00001 a
WHERE a.cod_sp = pcod_sp AND a.cod_sp_sec = pcod_sp_sec
LET xrnc = e41_solo_digitos(xrnc)
IF xrnc IS NULL OR (xrnc.getLength() <> 9 AND xrnc.getLength() <> 11) THEN
CALL fgl_winmessage("COMPRA ELECTRONICA",
"El suplidor "||pcod_sp||"-"||pcod_sp_sec||" no tiene cedula (11) o RNC (9) valido en el maestro de suplidores.\nCorrijalo antes de emitir el 41.","stop")
RETURN FALSE
END IF
LET xproximo = e41_proximo_ncf()
IF xproximo IS NULL THEN
RETURN FALSE
END IF
-- valores por defecto (si ya se habia capturado, se conservan)
IF NOT NVL(e41.activo, FALSE) THEN
INITIALIZE e41.* TO NULL
LET e41.porc41 = 3
LET e41.cisr41 = 3
LET e41.itbis41 = 'S'
LET e41.bs41 = '2'
END IF
LET e41.ncf41 = xproximo
LET e41.nom41 = xnom CLIPPED
LET e41.rnc41 = xrnc
OPEN WINDOW w_e41 WITH FORM "cpfmwd041"
ATTRIBUTES(STYLE="dialog", TEXT="Compra electronica (e-CF 41)")
CALL e41_carga_cisr()
LET int_flag = FALSE
INPUT BY NAME e41.neto41, e41.porc41, e41.cisr41, e41.itbis41, e41.bs41
WITHOUT DEFAULTS ATTRIBUTES(UNBUFFERED)
BEFORE INPUT
DISPLAY e41.ncf41, e41.nom41, e41.rnc41 TO ncf41, nom41, rnc41
CALL e41_recalcula()
ON CHANGE porc41
-- tipo de retencion sugerido segun el %
CASE e41.porc41
WHEN 3 LET e41.cisr41 = 3 -- otras rentas (servicios tecnicos)
WHEN 15 LET e41.cisr41 = 2 -- honorarios (alquiler = 1, escoger a mano)
WHEN 0 LET e41.cisr41 = NULL
END CASE
CALL e41_recalcula()
ON CHANGE itbis41
CALL e41_recalcula()
AFTER FIELD neto41
CALL e41_recalcula()
AFTER INPUT
IF NOT int_flag THEN
IF e41.neto41 IS NULL OR e41.neto41 <= 0 THEN
CALL fgl_winmessage("COMPRA ELECTRONICA","Digite el neto a pagar.","exclamation")
NEXT FIELD neto41
END IF
IF NOT porc_isr_valido41(e41.porc41) THEN
CALL fgl_winmessage("COMPRA ELECTRONICA","El % de ISR debe ser 0, 3 o 15.","exclamation")
NEXT FIELD porc41
END IF
IF e41.porc41 > 0 AND e41.cisr41 IS NULL THEN
CALL fgl_winmessage("COMPRA ELECTRONICA","Escoja el tipo de retencion ISR (formato 606).","exclamation")
NEXT FIELD cisr41
END IF
CALL e41_recalcula()
END IF
END INPUT
CLOSE WINDOW w_e41
IF int_flag THEN
LET int_flag = FALSE
RETURN FALSE
END IF
LET e41.activo = TRUE
RETURN TRUE
END FUNCTION
#-----------------------------------------------------------------------------
# Guarda el 41 en cptb00041 (dentro de la transaccion de grabado)
#-----------------------------------------------------------------------------
FUNCTION e41_guarda(pcod_sp, pcod_sp_sec, pnum_doc, pfecha)
DEFINE pcod_sp, pcod_sp_sec SMALLINT,
pnum_doc CHAR(13),
pfecha DATE
INSERT INTO cptb00041 (cod_sp, cod_sp_sec, num_doc, ncf, fecha, rnc, neto, porc_isr,
codigo_isr, lleva_itbis, bien_servicio, subtotal, itbis,
isr_retenido, itbis_retenido, total, us_crea, fech_crea)
VALUES (pcod_sp, pcod_sp_sec, pnum_doc, e41.ncf41, pfecha, e41.rnc41, e41.neto41,
e41.porc41, e41.cisr41, e41.itbis41, e41.bs41, e41.sub41, e41.itb41,
e41.isr41, e41.itbret41, e41.total41, usuarios, getdate())
IF STATUS < 0 THEN
CALL fgl_winmessage("COMPRA ELECTRONICA","Error guardando el e-CF 41 (cptb00041): "||SQLCA.sqlcode,"stop")
RETURN FALSE
END IF
RETURN TRUE
END FUNCTION
#-----------------------------------------------------------------------------
# Arma y envia el XML del 41 a la DGII (despues del COMMIT) y guarda la
# respuesta en cptb00041. Muestra el estado al usuario.
#-----------------------------------------------------------------------------
FUNCTION e41_envia(pcod_sp, pcod_sp_sec, pnum_doc, pfecha_orig, pfecha_proc, pdetalle)
DEFINE pcod_sp, pcod_sp_sec SMALLINT,
pnum_doc CHAR(13),
pfecha_orig, pfecha_proc DATE,
pdetalle CHAR(30)
DEFINE documento_encabezado RECORD
tipoEcf, eNcf, indicadorMontoGravado, tipoPago, fechaLimitePago, FormadePAgo, montoPago, FechaVencimientoSecuencia,
rncEmisor, razonSocialEmisor, direccionEmisor, Municipio, Provincia, telefonoEmisor,
correoEmisor, webSite, codigoVendedor, numeroFacturaInterna, numeroPedidoInterno,
fechaEmision, rncComprador, razonSocialComprador, montoGravadoTotal, montoGravadoI1,
montoGravadoI2, montoGravadoI3, montoExento, itbis1, itbis2, itbis3, totalItbis, totalItbis1,
totalItbis2, totalItbis3, montoTotal, tipoIngreso, terminoPago, TipoCuentaPago, NumeroCuentaPago, IdentificadorExtranjero,
contactoComprador, CorreoComprador, DireccionComprador, MunicipioComprador, ProvinciaComprador, FechaEntrega, FechaOrdenCompra,
NumeroOrdenCompra, CodigoInternoComprador,
nombreFile, IndicadorNotaCredito, NCFmodificado, codigoModificacion, fechaNCFmodificado STRING
END RECORD,
documento_Detalle DYNAMIC ARRAY OF RECORD
indicadorFacturacion, nombreItem, indicadorBienoServicio, cantidadItem, precioUnitarioItem,
montoItem STRING,
DescripcionItem VARCHAR(1000),
NumeroLinea INT
END RECORD,
ret41 DYNAMIC ARRAY OF RECORD
itbisRetenido DECIMAL(12,2),
isrRetenido DECIMAL(12,2)
END RECORD
DEFINE xrnc, xnombre, xdireccion VARCHAR(150),
xprov, xmuni VARCHAR(20),
xvence DATE,
xamb STRING,
phash, respuesta, xrespuesta, codigo, xhash STRING,
fechafirma DATETIME YEAR TO SECOND,
mensaje_trunc VARCHAR(2000),
idx INT
DEFINE Pok RECORD
trackId, ERROR, mensaje STRING
END RECORD
DEFINE p RECORD
trackid STRING,
codigo, estado, rnc, encf, secuenciaUtilizada, fechaRecepcion STRING,
mensajes DYNAMIC ARRAY OF RECORD
valor, codigo STRING
END RECORD
END RECORD
-- emisor
SELECT a.rnc, a.nombre, a.direccion, a.id_dgii, a.id_municipio_dgii
INTO xrnc, xnombre, xdireccion, xprov, xmuni
FROM companias a
SELECT MAX(a.fecha_vencimiento) INTO xvence FROM vetb00071 a
WHERE a.tipo_doc = 'FT' AND a.codigo_dgii = '41'
LET documento_encabezado.tipoEcf = "41"
LET documento_encabezado.eNcf = e41.ncf41
LET documento_encabezado.nombreFile = xrnc CLIPPED, e41.ncf41 CLIPPED, ".xml"
LET documento_encabezado.FechaVencimientoSecuencia = xvence USING "dd-mm-yyyy"
LET documento_encabezado.rncEmisor = xrnc CLIPPED
LET documento_encabezado.razonSocialEmisor = xnombre CLIPPED
LET documento_encabezado.direccionEmisor = xdireccion CLIPPED
LET documento_encabezado.Provincia = xprov CLIPPED
LET documento_encabezado.Municipio = xmuni CLIPPED
LET documento_encabezado.fechaEmision = pfecha_orig USING "dd-mm-yyyy"
-- va a cuentas por pagar: credito, vence en la fecha de proceso
LET documento_encabezado.tipoPago = '2'
LET documento_encabezado.fechaLimitePago = NVL(pfecha_proc, pfecha_orig) USING "dd-mm-yyyy"
LET documento_encabezado.FormadePAgo = '4'
-- Comprador = el proveedor
LET documento_encabezado.rncComprador = e41.rnc41
LET documento_encabezado.razonSocialComprador = e41.nom41
-- precios SIN ITBIS
LET documento_encabezado.indicadorMontoGravado = '0'
IF NVL(e41.itb41, 0) > 0 THEN
LET documento_encabezado.montoGravadoTotal = e41.sub41 USING "<<<<<<<<<<<<<&.&&"
LET documento_encabezado.montoGravadoI1 = e41.sub41 USING "<<<<<<<<<<<<<&.&&"
LET documento_encabezado.itbis1 = '18'
LET documento_encabezado.totalItbis = e41.itb41 USING "<<<<<<<<<<<<<&.&&"
LET documento_encabezado.totalItbis1 = e41.itb41 USING "<<<<<<<<<<<<<&.&&"
ELSE
LET documento_encabezado.montoExento = e41.sub41 USING "<<<<<<<<<<<<<&.&&"
END IF
LET documento_encabezado.montoTotal = e41.total41 USING "<<<<<<<<<<<<<&.&&"
LET documento_encabezado.montoPago = documento_encabezado.montoTotal
-- una linea con el concepto de la factura
IF NVL(e41.itb41, 0) > 0 THEN
LET documento_Detalle[1].indicadorFacturacion = 1
ELSE
LET documento_Detalle[1].indicadorFacturacion = 4
END IF
LET documento_Detalle[1].nombreItem = NVL(pdetalle CLIPPED, "COMPRA A PROVEEDOR INFORMAL")
LET documento_Detalle[1].indicadorBienoServicio = NVL(e41.bs41, '2')
LET documento_Detalle[1].cantidadItem = "1.00"
LET documento_Detalle[1].precioUnitarioItem = e41.sub41 USING "<<<<<<<<<<<<<&.&&"
LET documento_Detalle[1].montoItem = e41.sub41 USING "<<<<<<<<<<<<<&.&&"
LET ret41[1].itbisRetenido = NVL(e41.itbret41, 0)
LET ret41[1].isrRetenido = NVL(e41.isr41, 0)
CALL generafile41(documento_encabezado.*, documento_Detalle, ret41, 1, 1, 'adicionar')
RETURNING phash, respuesta, fechafirma, codigo
LET mensaje_trunc = respuesta
UPDATE cptb00041 SET hash = phash, ultima_respuesta = respuesta, fechafirmadigital = fechafirma
WHERE cod_sp = pcod_sp AND cod_sp_sec = pcod_sp_sec AND num_doc = pnum_doc
-- respuesta del envio
IF codigo = '200' THEN
TRY
CALL util.JSON.parse(respuesta, Pok)
CATCH
LET Pok.trackId = NULL
LET Pok.mensaje = 'ERROR EN PARSE'
END TRY
ELSE
LET Pok.trackId = NULL
LET Pok.mensaje = respuesta
END IF
-- estado en la DGII
LET p.estado = NULL
LET p.trackid = NULL
CALL p.mensajes.clear()
IF codigo = '200' THEN
LET xamb = ambiente()
CALL procesoEncf(' ', xamb.trim(), ' ', 0, 0, 'consultatrackid', respuesta)
RETURNING xhash, xrespuesta, codigo
TRY
CALL util.JSON.parse(xrespuesta, p)
CATCH
LET p.estado = 'SIN CONEXION'
LET p.mensajes[1].valor = 'NO SE PUDO CONSULTAR EL ESTADO EN LA DGII'
END TRY
END IF
IF p.estado = ' ' OR p.estado IS NULL THEN
LET p.estado = 'ERROR'
END IF
IF p.trackid IS NULL THEN
LET p.trackid = Pok.trackId
END IF
LET mensaje_trunc = NVL(Pok.mensaje, mensaje_trunc)
IF p.mensajes.getLength() > 0 THEN
LET mensaje_trunc = NULL
FOR idx = 1 TO p.mensajes.getLength()
IF p.mensajes[idx].valor IS NOT NULL THEN
IF mensaje_trunc IS NULL THEN
LET mensaje_trunc = p.mensajes[idx].valor
ELSE
LET mensaje_trunc = mensaje_trunc CLIPPED, " | ", p.mensajes[idx].valor
END IF
END IF
END FOR
END IF
UPDATE cptb00041 SET trackid = p.trackid, estado_dgii = p.estado, mensaje = mensaje_trunc
WHERE cod_sp = pcod_sp AND cod_sp_sec = pcod_sp_sec AND num_doc = pnum_doc
IF UPSHIFT(p.estado) MATCHES "*RECHAZ*" THEN
CALL fgl_winmessage("COMPRA ELECTRONICA RECHAZADA POR LA DGII",
"e-NCF "||e41.ncf41 CLIPPED||"\n\nMotivo: "||NVL(mensaje_trunc,"(sin mensaje)"),"stop")
ELSE
CALL fgl_winmessage("COMPRA ELECTRONICA",
"e-NCF "||e41.ncf41 CLIPPED||"\nEstado DGII: "||p.estado||"\n"||NVL(mensaje_trunc,""),"info")
END IF
END FUNCTION
#-----------------------------------------------------------------------------
# e-CF 41 COMPRAS: la empresa le compra a una persona sin RNC / sin NCF.
# Diferencias con el 43:
# - Lleva bloque Comprador = datos del PROVEEDOR (cedula y nombre),
# se pasan en documento_encabezado.rncComprador / razonSocialComprador.
# - Puede llevar ITBIS (IndicadorFacturacion 1/2/3 por linea, 4 = exento).
# - Lleva retenciones por linea y totales (ITBIS retenido e ISR retenido).
# ret41 va alineado por indice con documento_Detalle (linea k <-> ret41[k]).
# Los totales gravado/exento/ITBIS se toman de documento_encabezado tal como
# cpprmt006 ya los llena para generafile. MontoTotal = antes de retenciones.
# Confirmar orden/obligatoriedad de tags contra el XSD e-CF 41 en TesteCF.
#-----------------------------------------------------------------------------
FUNCTION generafile41(documento_encabezado, documento_Detalle, ret41, xsucid, xbodega, xoperacion)
DEFINE documento_encabezado RECORD
tipoEcf, eNcf, indicadorMontoGravado, tipoPago, fechaLimitePago, FormadePAgo, montoPago, FechaVencimientoSecuencia,
rncEmisor, razonSocialEmisor, direccionEmisor, Municipio, Provincia, telefonoEmisor,
correoEmisor, webSite, codigoVendedor, numeroFacturaInterna, numeroPedidoInterno,
fechaEmision, rncComprador, razonSocialComprador, montoGravadoTotal, montoGravadoI1,
montoGravadoI2, montoGravadoI3, montoExento, itbis1, itbis2, itbis3, totalItbis, totalItbis1,
totalItbis2, totalItbis3, montoTotal, tipoIngreso, terminoPago, TipoCuentaPago, NumeroCuentaPago, IdentificadorExtranjero,
contactoComprador, CorreoComprador, DireccionComprador, MunicipioComprador, ProvinciaComprador, FechaEntrega, FechaOrdenCompra,
NumeroOrdenCompra, CodigoInternoComprador,
nombreFile, IndicadorNotaCredito, NCFmodificado, codigoModificacion, fechaNCFmodificado STRING
END RECORD,
documento_Detalle DYNAMIC ARRAY OF RECORD
indicadorFacturacion, nombreItem, indicadorBienoServicio, cantidadItem, precioUnitarioItem,
montoItem STRING,
DescripcionItem VARCHAR(1000),
NumeroLinea INT
END RECORD,
ret41 DYNAMIC ARRAY OF RECORD
itbisRetenido DECIMAL(12,2),
isrRetenido DECIMAL(12,2)
END RECORD,
xoperacion VARCHAR(30),
xsucid, xbodega INT
DEFINE archivo, respuesta, codigo, phash, chFirma STRING,
a om.SaxAttributes,
fechaFirma DATETIME YEAR TO SECOND,
k, linea INTEGER,
itbisRet, isrRet, totItbisRet, totIsrRet DECIMAL(14,2),
xamb STRING
-- En el 41 el proveedor (cedula y nombre) es obligatorio
IF documento_encabezado.rncComprador IS NULL
OR documento_encabezado.razonSocialComprador IS NULL THEN
RETURN NULL, "El 41 requiere cedula y nombre del proveedor", NULL, "0"
END IF
LET archivo = fgl_getenv("RUTA_XML_ORIGINALES"), documento_encabezado.nombreFile
TRY
LET wcp = om.XmlWriter.createFileWriter(archivo)
CATCH
RETURN NULL, "No se pudo crear el XML del 41 (RUTA_XML_ORIGINALES)", NULL, "0"
END TRY
IF wcp IS NULL THEN
RETURN NULL, "No se pudo crear el XML del 41 (RUTA_XML_ORIGINALES)", NULL, "0"
END IF
LET a = om.SaxAttributes.create()
CALL wcp.startDocument()
CALL a.addAttribute("xmlns:xs", "http://www.w3.org/2001/XMLSchema")
CALL wcp.startElement("ECF", a)
CALL a.clear()
#-------------------- Encabezado --------------------
CALL wcp.startElement("Encabezado", NULL)
CALL tagcp("Version", "1.0")
-- IdDoc (el 41 NO lleva TipoIngresos)
CALL wcp.startElement("IdDoc", NULL)
CALL tagcp("TipoeCF", "41")
CALL tagcp("eNCF", documento_encabezado.eNcf CLIPPED)
IF documento_encabezado.FechaVencimientoSecuencia IS NOT NULL THEN
CALL tagcp("FechaVencimientoSecuencia", documento_encabezado.FechaVencimientoSecuencia)
END IF
IF montocp(documento_encabezado.montoGravadoTotal) > 0 THEN
CALL tagcp("IndicadorMontoGravado", NVL(documento_encabezado.indicadorMontoGravado, "0"))
END IF
CALL tagcp("TipoPago", NVL(documento_encabezado.tipoPago, "1"))
IF documento_encabezado.tipoPago = "2" AND documento_encabezado.fechaLimitePago IS NOT NULL THEN
CALL tagcp("FechaLimitePago", documento_encabezado.fechaLimitePago)
END IF
CALL wcp.startElement("TablaFormasPago", NULL)
CALL wcp.startElement("FormaDePago", NULL)
CALL tagcp("FormaPago", NVL(documento_encabezado.FormadePAgo, "1"))
CALL tagcp("MontoPago", documento_encabezado.montoTotal)
CALL wcp.endElement("FormaDePago")
CALL wcp.endElement("TablaFormasPago")
CALL wcp.endElement("IdDoc")
-- Emisor (igual que el 43)
CALL wcp.startElement("Emisor", NULL)
CALL tagcp("RNCEmisor", documento_encabezado.rncEmisor)
CALL tagcp("RazonSocialEmisor", cortacp(documento_encabezado.razonSocialEmisor, 150))
CALL tagcp("NombreComercial", cortacp(documento_encabezado.razonSocialEmisor, 150))
CALL tagcp("DireccionEmisor", cortacp(documento_encabezado.direccionEmisor, 100))
IF codigo6cp(documento_encabezado.Municipio) IS NOT NULL THEN
CALL tagcp("Municipio", codigo6cp(documento_encabezado.Municipio))
END IF
IF codigo6cp(documento_encabezado.Provincia) IS NOT NULL THEN
CALL tagcp("Provincia", codigo6cp(documento_encabezado.Provincia))
END IF
IF documento_encabezado.correoEmisor IS NOT NULL THEN
CALL tagcp("CorreoEmisor", documento_encabezado.correoEmisor)
END IF
IF documento_encabezado.numeroFacturaInterna IS NOT NULL THEN
CALL tagcp("NumeroFacturaInterna", documento_encabezado.numeroFacturaInterna)
END IF
CALL tagcp("FechaEmision", documento_encabezado.fechaEmision)
CALL wcp.endElement("Emisor")
-- Comprador = el PROVEEDOR al que se le compra
CALL wcp.startElement("Comprador", NULL)
CALL tagcp("RNCComprador", documento_encabezado.rncComprador CLIPPED)
CALL tagcp("RazonSocialComprador", cortacp(documento_encabezado.razonSocialComprador, 150))
CALL wcp.endElement("Comprador")
-- Totales de retencion (se suman de las lineas que se van a enviar)
LET totItbisRet = 0
LET totIsrRet = 0
FOR k = 1 TO documento_Detalle.getLength()
IF documento_Detalle[k].nombreItem IS NULL
AND documento_Detalle[k].DescripcionItem IS NULL THEN
CONTINUE FOR
END IF
IF k <= ret41.getLength() THEN
LET totItbisRet = totItbisRet + NVL(ret41[k].itbisRetenido, 0)
LET totIsrRet = totIsrRet + NVL(ret41[k].isrRetenido, 0)
END IF
END FOR
-- Totales (solo se escriben los montos que aplican)
CALL wcp.startElement("Totales", NULL)
CALL tagcpmonto("MontoGravadoTotal", documento_encabezado.montoGravadoTotal)
CALL tagcpmonto("MontoGravadoI1", documento_encabezado.montoGravadoI1)
CALL tagcpmonto("MontoGravadoI2", documento_encabezado.montoGravadoI2)
CALL tagcpmonto("MontoGravadoI3", documento_encabezado.montoGravadoI3)
CALL tagcpmonto("MontoExento", documento_encabezado.montoExento)
IF montocp(documento_encabezado.montoGravadoI1) > 0 THEN
CALL tagcp("ITBIS1", NVL(documento_encabezado.itbis1, "18"))
END IF
IF montocp(documento_encabezado.montoGravadoI2) > 0 THEN
CALL tagcp("ITBIS2", NVL(documento_encabezado.itbis2, "16"))
END IF
IF montocp(documento_encabezado.montoGravadoI3) > 0 THEN
CALL tagcp("ITBIS3", "0")
END IF
IF montocp(documento_encabezado.montoGravadoTotal) > 0 THEN
CALL tagcp("TotalITBIS", NVL(documento_encabezado.totalItbis, "0.00"))
CALL tagcpmonto("TotalITBIS1", documento_encabezado.totalItbis1)
CALL tagcpmonto("TotalITBIS2", documento_encabezado.totalItbis2)
IF montocp(documento_encabezado.montoGravadoI3) > 0 THEN
CALL tagcp("TotalITBIS3", "0.00")
END IF
END IF
CALL tagcp("MontoTotal", documento_encabezado.montoTotal)
CALL tagcp("TotalITBISRetenido", totItbisRet USING "<<<<<<<<<<<<&.&&")
CALL tagcp("TotalISRRetencion", totIsrRet USING "<<<<<<<<<<<<&.&&")
CALL wcp.endElement("Totales")
CALL wcp.endElement("Encabezado")
#-------------------- DetallesItems --------------------
CALL wcp.startElement("DetallesItems", NULL)
LET linea = 0
FOR k = 1 TO documento_Detalle.getLength()
IF documento_Detalle[k].nombreItem IS NULL
AND documento_Detalle[k].DescripcionItem IS NULL THEN
CONTINUE FOR
END IF
LET linea = linea + 1
LET itbisRet = 0
LET isrRet = 0
IF k <= ret41.getLength() THEN
LET itbisRet = NVL(ret41[k].itbisRetenido, 0)
LET isrRet = NVL(ret41[k].isrRetenido, 0)
END IF
CALL wcp.startElement("Item", NULL)
CALL tagcp("NumeroLinea", linea USING "<<<<")
CALL tagcp("IndicadorFacturacion", NVL(documento_Detalle[k].indicadorFacturacion, "4"))
-- Retencion: en el 41 la empresa actua como agente de retencion
CALL wcp.startElement("Retencion", NULL)
CALL tagcp("IndicadorAgenteRetencionoPercepcion", "1")
CALL tagcp("MontoITBISRetenido", itbisRet USING "<<<<<<<<<<<<&.&&")
CALL tagcp("MontoISRRetenido", isrRet USING "<<<<<<<<<<<<&.&&")
CALL wcp.endElement("Retencion")
CALL tagcp("NombreItem",
NVL(documento_Detalle[k].nombreItem, documento_Detalle[k].DescripcionItem))
CALL tagcp("IndicadorBienoServicio", NVL(documento_Detalle[k].indicadorBienoServicio, "2"))
CALL tagcp("CantidadItem", NVL(documento_Detalle[k].cantidadItem, "1"))
CALL tagcp("PrecioUnitarioItem", documento_Detalle[k].precioUnitarioItem)
CALL tagcp("MontoItem", documento_Detalle[k].montoItem)
CALL wcp.endElement("Item")
END FOR
CALL wcp.endElement("DetallesItems")
#-------------------- Firma --------------------
LET fechaFirma = CURRENT
LET chFirma = util.Datetime.format(fechaFirma, "%d-%m-%Y %H:%M:%S")
CALL tagcp("FechaHoraFirma", chFirma)
CALL wcp.endElement("ECF")
CALL wcp.endDocument()
#-------------------- Firmar y enviar (igual que el 43) --------------------
-- ambiente DGII de la empresa (companias.ambienteDgii, funcion ambiente())
LET xamb = ambiente()
LET xamb = xamb.trim()
IF xamb IS NULL OR (xamb <> "test" AND xamb <> "certificacion" AND xamb <> "produccion") THEN
RETURN NULL, "companias.ambienteDgii debe ser test, certificacion o produccion. No se envio a la DGII.", fechaFirma, "0"
END IF
CALL procesoEncf(archivo, xamb, "normal", xsucid, xbodega, xoperacion, ' ')
RETURNING phash, respuesta, codigo
RETURN phash, respuesta, fechaFirma, codigo
END FUNCTION
#-----------------------------------------------------------------------------
# Convierte un monto STRING a decimal (NULL o vacio = 0)
FUNCTION montocp(valor STRING) RETURNS DECIMAL(14,2)
DEFINE n DECIMAL(14,2)
LET n = NULL
TRY
LET n = valor
CATCH
LET n = NULL
END TRY
RETURN NVL(n, 0)
END FUNCTION
#-----------------------------------------------------------------------------
# Recorta un texto al largo maximo que permite el XSD
FUNCTION cortacp(valor STRING, largo INTEGER) RETURNS STRING
LET valor = valor.trim()
IF valor.getLength() > largo THEN
LET valor = valor.subString(1, largo)
END IF
RETURN valor.trim()
END FUNCTION
#-----------------------------------------------------------------------------
# Provincia / Municipio DGII: codigo de 6 digitos con ceros a la izquierda
# (10100 -> 010100). NULL o 0 = no se envia.
FUNCTION codigo6cp(valor STRING) RETURNS STRING
DEFINE n INTEGER
LET n = NULL
TRY
LET n = valor
CATCH
LET n = NULL
END TRY
IF n IS NULL OR n = 0 THEN
RETURN NULL
END IF
RETURN n USING "&&&&&&"
END FUNCTION
#-----------------------------------------------------------------------------
# Escribe el tag solo si el monto es mayor que cero
FUNCTION tagcpmonto(nombre STRING, valor STRING)
IF montocp(valor) > 0 THEN
CALL tagcp(nombre, valor)
END IF
END FUNCTION
#-----------------------------------------------------------------------------
FUNCTION tagcp(nombre STRING, valor STRING)
CALL wcp.startElement(nombre, NULL)
CALL wcp.characters(valor)
CALL wcp.endElement(nombre)
END FUNCTION