Files
MBS/PROYECTO/vedir/veprmt023.4gl
T

1144 lines
38 KiB
Plaintext

IMPORT os
GLOBALS
"veprgb000.4gl"
DEFINE arr_ofertas DYNAMIC ARRAY OF RECORD
id int,
cod_n smallint,
cod_grupo smallint,
cod_tipo smallint,
cod_sec smallint,
descripcion VARCHAR(100),
unidad_med VARCHAR(4),
porc_desc DEC(12,2),
fecha_inicial,fecha_final DATE
END RECORD,
datos_c RECORD
documento INT,
fecha DATE,
descripcion_oferta VARCHAR(60),
porc_desc1 DEC(12,2),
fecha1 DATETIME YEAR TO MINUTE,
fecha2 DATETIME YEAR TO MINUTE
END RECORD
DEFINE xus_crea,xus_mod VARCHAR(50),
xfech_crea,xfech_mod DATETIME YEAR TO MINUTE,
adiciona BOOLEAN,
porc_Desc1 DEC(12,2),
no_registros INT
DEFINE arr_raw DYNAMIC ARRAY OF RECORD
c1,c2,c3,c4,c5,c6,c7,c8 STRING
END RECORD
DEFINE arr_fields DYNAMIC ARRAY OF STRING
DEFINE arr_shared DYNAMIC ARRAY OF STRING
DEFINE g_diag STRING
MAIN
DEFER INTERRUPT
CALL STARTLOG("veprmt023.log")
CALL ARG_VAL(1) RETURNING usuarios
CALL ARG_VAL(2) RETURNING clave
CONNECT TO "smarmotech" AS "MSSQL" USER usuarios USING clave
CALL veprmt023()
END MAIN
FUNCTION veprmt023()
OPEN WINDOW w1 AT 1,1 WITH FORM "vefmmt023"
MENU
ON ACTION nuevo
LET adiciona = TRUE
CALL veprad23()
ON ACTION modificar
LET adiciona = FALSE
CALL veprmf023()
ON ACTION salir
EXIT MENU
END MENU
CLOSE WINDOW w1
END FUNCTION
FUNCTION veprad23()
DIALOG ATTRIBUTES(UNBUFFERED, FIELD ORDER FORM)
INPUT BY NAME datos_c.*
BEFORE INPUT
LET datos_c.fecha = TODAY
DISPLAY BY NAME datos_c.fecha
NEXT FIELD descripcion_oferta
AFTER INPUT
IF datos_c.fecha2 < datos_c.fecha1 THEN
CALL fgl_winmessage("ERROR","FECHA FINAL NO PUEDE SER MENOR A LA INICIAL","INFO")
NEXT FIELD fecha1
END IF
END INPUT
CONSTRUCT criterio ON a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_sec,a.descrip_esp FROM
codn,codgrupo,codtipo,codsec,descrip_esp
ON ACTION ejecutabusqueda
CALL buscarCriterio()
AFTER CONSTRUCT
END CONSTRUCT
INPUT ARRAY arr_ofertas FROM sofertas.* ATTRIBUTE(WITHOUT DEFAULTS)
AFTER FIELD cod_sec
IF arr_ofertas[arr_curr()].cod_sec IS NOT NULL THEN
CALL busca_producto(arr_ofertas[arr_curr()].cod_n,arr_ofertas[arr_curr()].cod_grupo,
arr_ofertas[arr_curr()].cod_tipo,arr_ofertas[arr_curr()].cod_sec) RETURNING arr_ofertas[arr_curr()].descripcion
DISPLAY arr_ofertas[arr_curr()].descripcion TO sofertas[scr_line()].descripcion
END IF
ON ACTION guardar
IF fgl_winquestion("GUARDAR","ESTAS SEGURO DE GUARDAR ESTAS INFORMACIONES?","YES","YES|NO","QUESTION",0) THEN
CALL actualiza()
END IF
ON ACTION copiar
FOR idx = 1 TO arr_ofertas.getLength()
LET arr_ofertas[idx].fecha_inicial=datos_c.fecha1
LET arr_ofertas[idx].fecha_final=datos_c.fecha2
LET arr_ofertas[idx].porc_desc=datos_c.porc_desc1
END FOR
ON ACTION importar
CALL importar_hoja()
AFTER INPUT
IF INT_FLAG THEN
LET INT_FLAG = FALSE
CALL msg(2)
EXIT PROGRAM
END IF
FOR idx = 1 TO arr_ofertas.getLength()
IF arr_ofertas[idx].cod_sec IS NOT NULL THEN
IF arr_ofertas[idx].porc_desc IS NULL THEN
CALL msg(16)
NEXT FIELD porc_desc
END IF
IF arr_ofertas[idx].fecha_inicial IS NULL THEN
CALL msg(16)
NEXT FIELD fecha_inicial
END IF
IF arr_ofertas[idx].fecha_final IS NULL THEN
CALL msg(16)
NEXT FIELD fecha_final
END IF
END IF
END FOR
END INPUT
ON ACTION busca_productos
CONSTRUCT criterio ON a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_sec FROM cod_n,cod_grupo,cod_tipo,cod_sec
BEFORE CONSTRUCT
CALL arr_ofertas.clear()
END CONSTRUCT
LET selec = "SELECT a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_sec,a.descrip_esp,a.unidad_med FROM iptb00002 a WHERE a.status_t IS NULL and ",
criterio CLIPPED
PREPARE comando FROM selec
DECLARE busca CURSOR FOR comando
LET idx = 1
FOREACH busca INTO arr_ofertas[idx].cod_n,arr_ofertas[idx].cod_grupo,arr_ofertas[idx].cod_tipo,arr_ofertas[idx].cod_sec,
arr_ofertas[idx].descripcion,arr_ofertas[idx].unidad_med
LET arr_ofertas[idx].id=idx
LET idx = idx + 1
END FOREACH
LET no_registros=arr_ofertas.getLength()
DISPLAY BY NAME no_registros
ON ACTION CANCEL
EXIT PROGRAM
END DIALOG
END FUNCTION
FUNCTION veprmf023()
CONSTRUCT BY NAME criterio on a.documento,a.fecha,a.descripcion_oferta
ON ACTION CANCEL
LET INT_FLAG = FALSE
EXIT PROGRAM
END CONSTRUCT
LET selec = #"SELECT a.documento,CONVERT(CHAR(10),a.fecha,103),a.descripcion_oferta,' ',' ',' ',a.us_crea,a.fech_crea ,a.us_mod,a.fech_mod FROM vetb00032 a ",
"SELECT a.documento,a.fecha,a.descripcion_oferta FROM vetb00032 a ",
" WHERE ",criterio CLIPPED
PREPARE comando2 FROM selec
DECLARE busca_c SCROLL CURSOR FOR comando2
OPEN busca_c
FETCH FIRST busca_c INTO datos_c.*#,xus_crea,xfech_crea,xus_mod,xfech_mod
CALL desplega_info()
MENU
ON ACTION siguiente
FETCH NEXT busca_c INTO datos_c.*,xus_crea,xfech_crea,xus_mod,xfech_mod
CALL desplega_info()
ON ACTION anterior
FETCH previous busca_c INTO datos_c.*,xus_crea,xfech_crea,xus_mod,xfech_mod
CALL desplega_info()
ON ACTION primero
FETCH FIRST busca_c INTO datos_c.*,xus_crea,xfech_crea,xus_mod,xfech_mod
CALL desplega_info()
ON ACTION ultimo
FETCH LAST busca_c INTO datos_c.*,xus_crea,xfech_crea,xus_mod,xfech_mod
CALL desplega_info()
ON ACTION escoger
INPUT BY NAME datos_c.* WITHOUT DEFAULTS
BEFORE INPUT
DISPLAY BY NAME datos_c.fecha
NEXT FIELD descripcion_oferta
AFTER INPUT
IF datos_c.fecha2 < datos_c.fecha1 THEN
CALL fgl_winmessage("ERROR","FECHA FINAL NO PUEDE SER MENOR A LA INICIAL","INFO")
NEXT FIELD fecha1
END IF
END INPUT
INPUT ARRAY arr_ofertas FROM sofertas.* ATTRIBUTE(WITHOUT DEFAULTS)
ON ACTION copiar
FOR idx = 1 TO arr_ofertas.getLength()
LET arr_ofertas[idx].fecha_inicial=datos_c.fecha1
LET arr_ofertas[idx].fecha_final=datos_c.fecha2
LET arr_ofertas[idx].porc_desc=datos_c.porc_desc1
END FOR
AFTER FIELD cod_sec
IF arr_ofertas[arr_curr()].cod_sec IS NOT NULL THEN
CALL busca_producto(arr_ofertas[arr_curr()].cod_n,arr_ofertas[arr_curr()].cod_grupo,
arr_ofertas[arr_curr()].cod_tipo,arr_ofertas[arr_curr()].cod_sec) RETURNING arr_ofertas[arr_curr()].descripcion
DISPLAY arr_ofertas[arr_curr()].descripcion TO sofertas[scr_line()].descripcion
END IF
AFTER FIELD fecha_final
IF arr_ofertas[arr_curr()].cod_sec IS NOT NULL THEN
IF arr_ofertas[arr_curr()].fecha_final < arr_ofertas[arr_curr()].fecha_inicial THEN
CALL fgl_winmessage("INFO","FECHA FINAL NO PUEDE SER MENOR A LA INCIAL","INFO")
NEXT FIELD fecha_final
END IF
END IF
ON ACTION guardar ATTRIBUTE(TEXT="Salvar",image="save")
IF fgl_winquestion("GUARDAR","ESTAS SEGURO DE GUARDAR ESTAS INFORMACIONES?","YES","YES|NO","QUESTION",0) THEN
CALL actualiza()
END IF
AFTER INPUT
IF INT_FLAG THEN
LET INT_FLAG = FALSE
CALL msg(2)
EXIT PROGRAM
END IF
FOR idx = 1 TO arr_ofertas.getLength()
IF arr_ofertas[idx].cod_sec IS NOT NULL THEN
IF arr_ofertas[idx].porc_desc IS NULL THEN
CALL msg(16)
NEXT FIELD porc_desc
END IF
IF arr_ofertas[idx].fecha_inicial IS NULL THEN
CALL msg(16)
NEXT FIELD fecha_inicial
END IF
IF arr_ofertas[idx].fecha_final IS NULL THEN
CALL msg(16)
NEXT FIELD fecha_final
END IF
END IF
END FOR
END INPUT
ON ACTION salir
EXIT MENU
END MENU
END FUNCTION
FUNCTION actualiza()
DEFINE vfdesc DATE
FOR idx = 1 TO arr_ofertas.getLength()
IF arr_ofertas[idx].cod_sec IS NOT NULL THEN
IF arr_ofertas[idx].porc_desc IS NULL THEN
CALL msg(16)
RETURN
END IF
IF arr_ofertas[idx].fecha_inicial IS NULL THEN
CALL msg(16)
RETURN
END IF
IF arr_ofertas[idx].fecha_final IS NULL THEN
CALL msg(16)
RETURN
END IF
END IF
END FOR
BEGIN WORK
IF adiciona THEN
SELECT MAX(a.documento) INTO datos_c.documento FROM vetb00032 a
IF datos_c.documento IS NULL THEN
LET datos_c.documento = 0
END IF
LET datos_c.documento = datos_c.documento+1
# la fecha del documento no admite nulos (p.ej. carga por Excel)
IF datos_c.fecha IS NULL THEN
LET datos_c.fecha = TODAY
END IF
IF datos_c.descripcion_oferta IS NULL OR datos_c.descripcion_oferta = " " THEN
LET datos_c.descripcion_oferta = "OFERTAS CARGADAS DESDE EXCEL"
END IF
INSERT INTO vetb00032 (documento,fecha,descripcion_oferta,us_crea,fech_crea)
VALUES (datos_c.documento,datos_c.fecha,datos_c.descripcion_oferta,usuarios,getdate())
ELSE
DELETE FROM vetb00033 WHERE documento =datos_c.documento
UPDATE vetb00032 SET descripcion_oferta = datos_c.descripcion_oferta,
us_mod = usuarios,
fech_mod = getdate()
WHERE documento = datos_c.documento
END IF
FOR idx = 1 TO arr_ofertas.getLength()
IF arr_ofertas[idx].cod_Sec IS NOT NULL THEN
# 'id' es IDENTITY: NO se inserta (lo genera SQL Server).
# 'precio' es NOT NULL en vetb00033: se guarda 0 (esta carga es solo de descuentos).
# La relacion entre cabecera y detalle es por 'documento'.
INSERT INTO vetb00033 (documento,cod_n,cod_grupo,cod_tipo,cod_sec,porc_desc,fecha_inicial,fecha_final,precio)
VALUES (datos_c.documento,arr_ofertas[idx].cod_n,arr_ofertas[idx].cod_grupo,arr_ofertas[idx].cod_tipo,
arr_ofertas[idx].cod_sec,
arr_ofertas[idx].porc_desc,arr_ofertas[idx].fecha_inicial,arr_ofertas[idx].fecha_final,0)
# fecha_desc = fecha_final + 1 dia: el sistema evalua la oferta con
# "fecha < fecha_desc", asi el ultimo dia de la oferta queda incluido.
LET vfdesc = arr_ofertas[idx].fecha_final + 1
UPDATE iptb00002 SET porc_desc = arr_ofertas[idx].porc_desc,fecha_desc = vfdesc,us_mod = usuarios,fech_mod = getdate()
WHERE cod_n = arr_ofertas[idx].cod_n AND cod_grupo = arr_ofertas[idx].cod_grupo AND cod_tipo = arr_ofertas[idx].cod_tipo AND
cod_sec = arr_ofertas[idx].cod_sec
END IF
END FOR
COMMIT WORK
CALL fgl_winmessage("INFO","ACTUALIZACION EXITOSA","INFO")
CALL arr_ofertas.clear()
END FUNCTION
FUNCTION busca_producto(xcodn,xcodgrupo,xcodtipo,xcodsec)
DEFINE xcodn,xcodgrupo,xcodtipo,xcodsec SMALLINT,
descripcion VARCHAR(100)
SELECT a.descrip_esp INTO descripcion FROM iptb00002 a
WHERE a.cod_n = xcodn AND a.cod_grupo = xcodgrupo AND
a.cod_tipo = xcodtipo AND a.cod_sec = xcodsec AND a.status_t IS NULL
RETURN descripcion
END FUNCTION
FUNCTION desplega_info()
DISPLAY BY NAME datos_c.*
DISPLAY xus_crea TO us_crea
DISPLAY xus_mod TO us_mod
DISPLAY xfech_crea TO fech_crea
DISPLAY xfech_mod TO fech_mod
DECLARE busca_c1 CURSOR FOR
SELECT a.id,a.cod_n,a.cod_grupo,a.cod_tipo,a.cod_sec,b.descrip_esp,b.unidad_med,a.porc_desc,a.fecha_inicial,a.fecha_final
FROM vetb00033 a,iptb00002 b
WHERE a.documento = datos_c.documento AND
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
LET idx = 1
FOREACH busca_c1 INTO arr_ofertas[idx].id, arr_ofertas[idx].cod_n,arr_ofertas[idx].cod_grupo,arr_ofertas[idx].cod_tipo,arr_ofertas[idx].cod_sec,
arr_ofertas[idx].descripcion,arr_ofertas[idx].unidad_med,arr_ofertas[idx].porc_desc,arr_ofertas[idx].fecha_inicial,
arr_ofertas[idx].fecha_final
LET arr_ofertas[idx].id=idx
LET idx = idx + 1
END FOREACH
END FUNCTION
FUNCTION buscarCriterio()
LET selec = "SELECT '1',a.cod_n,a.cod_grupo, a.cod_tipo ,a.cod_sec,a.descrip_esp,a.unidad_med,a.porc_desc,
' ',a.fecha_desc
FROM iptb00002 a WHERE a.status_t IS NULL AND ",criterio CLIPPED
PREPARE comando FROM selec
DECLARE buscaProd CURSOR FOR comando
LET idx = 1
FOREACH buscaProd INTO arr_ofertas[idx].*
LET idx = idx + 1
END FOREACH
END FUNCTION
#=====================================================================
# CARGA MASIVA DE OFERTAS DESDE UNA HOJA (Excel .xlsx o .csv)
# --------------------------------------------------------------------
# El usuario sube el archivo, se lee al arreglo arr_ofertas y luego,
# al presionar GUARDAR, se ejecuta actualiza(), que crea el documento
# de oferta (vetb00032 / vetb00033) y actualiza iptb00002 en las
# columnas porc_desc y fecha_desc (fecha de termino).
#
# Columnas esperadas de la plantilla (en este orden):
# 1 cod_n 2 cod_grupo 3 cod_tipo 4 cod_sec
# 5 % descuento 6 fecha_inicial (opcional) 7 fecha_final (termino)
# 8 descripcion (informativa; el sistema la toma de la base)
#
# El % se interpreta como entero (20 = 20%). Si el valor viene <= 1
# (ej. 0.20 de las hojas anteriores) se multiplica x100 automaticamente.
#=====================================================================
FUNCTION importar_hoja()
DEFINE filepath, ext, mensaje STRING
DEFINE d_tipos, f_tipos STRING
DEFINE dir_dest, dir_tmp, base_tmp STRING
DEFINE ok SMALLINT
LET d_tipos = "Hojas de oferta"
LET f_tipos = "*.xlsx *.csv *.txt"
LET mensaje = "Elige la hoja de ofertas (Excel .xlsx o CSV)"
LET filepath = NULL
CALL ui.Interface.frontCall("standard","openfile",
[".", d_tipos, f_tipos, mensaje],[filepath])
IF filepath IS NULL OR filepath.getLength() = 0 THEN
RETURN
END IF
LET ext = downshift(os.Path.extension(filepath))
IF ext <> "xlsx" AND ext <> "csv" AND ext <> "txt" THEN
CALL fgl_winmessage("IMPORTAR",
"El archivo debe ser .xlsx o .csv","stop")
RETURN
END IF
# carpeta temporal en el servidor
LET base_tmp = fgl_getenv("TEMP")
IF base_tmp IS NULL OR base_tmp.getLength() = 0 THEN
LET base_tmp = fgl_getenv("DBTEMP")
END IF
IF base_tmp IS NULL OR base_tmp.getLength() = 0 THEN
LET base_tmp = "."
END IF
LET dir_dest = base_tmp CLIPPED, "/oferta_", usuarios CLIPPED, ".", ext
# trae el archivo del cliente (GDC) al servidor
CALL fgl_getfile(filepath, dir_dest)
CALL arr_raw.clear()
IF ext = "xlsx" THEN
LET dir_tmp = base_tmp CLIPPED, "/ofertaxlsx_", usuarios CLIPPED
LET ok = leer_xlsx(dir_dest, dir_tmp)
IF NOT ok THEN
LET mensaje = "No se pudo leer el .xlsx.\n", g_diag CLIPPED,
"\n\nComo alternativa, guarde la hoja como CSV y suba el .csv."
CALL fgl_winmessage("IMPORTAR", mensaje, "stop")
RETURN
END IF
ELSE
CALL leer_csv(dir_dest)
END IF
CALL procesar_raw()
# si se cargaron ofertas, pedir la descripcion del documento
IF no_registros > 0 THEN
CALL pedir_descripcion()
END IF
END FUNCTION
#---------------------------------------------------------------------
# Solicita la descripcion de la oferta para el documento (vetb00032)
#---------------------------------------------------------------------
FUNCTION pedir_descripcion()
DEFINE resp, defecto STRING
LET defecto = datos_c.descripcion_oferta
IF defecto IS NULL OR defecto.trim().getLength() = 0 THEN
LET defecto = "OFERTAS ", TODAY USING "dd/mm/yyyy"
END IF
LET resp = fgl_winprompt(0, 0, "Descripcion de la oferta:", defecto CLIPPED, 60, 0)
IF resp IS NULL OR resp.trim().getLength() = 0 THEN
LET datos_c.descripcion_oferta = defecto
ELSE
LET datos_c.descripcion_oferta = resp
END IF
DISPLAY BY NAME datos_c.descripcion_oferta
END FUNCTION
#---------------------------------------------------------------------
# Lectura de archivo CSV (separador , o ; autodetectado)
#---------------------------------------------------------------------
FUNCTION leer_csv(pfile)
DEFINE pfile STRING
DEFINE ch base.Channel
DEFINE linea, delim STRING
DEFINE first SMALLINT
DEFINE n INT
LET ch = base.Channel.create()
CALL ch.openFile(pfile, "r")
LET first = TRUE
LET delim = ","
WHILE NOT ch.isEof()
LET linea = ch.readLine()
IF linea IS NULL THEN EXIT WHILE END IF
LET linea = linea.trim()
IF linea.getLength() = 0 THEN CONTINUE WHILE END IF
IF first THEN
IF linea.getIndexOf(";",1) > 0 THEN
LET delim = ";"
ELSE
LET delim = ","
END IF
LET first = FALSE
END IF
CALL parse_csv_line(linea, delim)
LET n = arr_raw.getLength() + 1
LET arr_raw[n].c1 = celda(1)
LET arr_raw[n].c2 = celda(2)
LET arr_raw[n].c3 = celda(3)
LET arr_raw[n].c4 = celda(4)
LET arr_raw[n].c5 = celda(5)
LET arr_raw[n].c6 = celda(6)
LET arr_raw[n].c7 = celda(7)
LET arr_raw[n].c8 = celda(8)
# cuando el separador es ';' el decimal suele venir con coma
IF delim = ";" AND arr_raw[n].c5.getIndexOf(",",1) > 0 THEN
LET arr_raw[n].c5 = replace_str(arr_raw[n].c5, ",", ".")
END IF
END WHILE
CALL ch.close()
END FUNCTION
FUNCTION celda(pi)
DEFINE pi INT
IF pi >= 1 AND pi <= arr_fields.getLength() THEN
RETURN arr_fields[pi]
END IF
RETURN ""
END FUNCTION
#---------------------------------------------------------------------
# Separa una linea CSV en arr_fields, respetando comillas dobles
#---------------------------------------------------------------------
FUNCTION parse_csv_line(pline, pdelim)
DEFINE pline, pdelim STRING
DEFINE i, len INT
DEFINE c STRING
DEFINE inq SMALLINT
DEFINE sb base.StringBuffer
CALL arr_fields.clear()
LET sb = base.StringBuffer.create()
LET len = pline.getLength()
LET inq = FALSE
LET i = 1
WHILE i <= len
LET c = pline.getCharAt(i)
IF inq THEN
IF c = "\"" THEN
IF i < len AND pline.getCharAt(i+1) = "\"" THEN
CALL sb.append("\"")
LET i = i + 1
ELSE
LET inq = FALSE
END IF
ELSE
CALL sb.append(c)
END IF
ELSE
IF c = "\"" THEN
LET inq = TRUE
ELSE
IF c = pdelim THEN
CALL arr_fields.appendElement()
LET arr_fields[arr_fields.getLength()] = sb.toString()
CALL sb.clear()
ELSE
CALL sb.append(c)
END IF
END IF
END IF
LET i = i + 1
END WHILE
CALL arr_fields.appendElement()
LET arr_fields[arr_fields.getLength()] = sb.toString()
END FUNCTION
#---------------------------------------------------------------------
# Lectura de archivo .xlsx (es un ZIP): se descomprime con 'tar' y se
# leen sheet1.xml + sharedStrings.xml directamente. Devuelve TRUE/FALSE
#---------------------------------------------------------------------
FUNCTION leer_xlsx(pfile, pdir)
DEFINE pfile, pdir STRING
DEFINE cmd, psheet, pshared, plog STRING
DEFINE bs, winroot, psexe, tarexe STRING
DEFINE l_ok SMALLINT
LET g_diag = ""
# el archivo subido debe haber llegado al servidor
IF NOT os.Path.exists(pfile) THEN
LET g_diag = "El archivo no llego al servidor: ", pfile CLIPPED
RETURN FALSE
END IF
LET psheet = pdir CLIPPED, "/xl/worksheets/sheet1.xml"
LET pshared = pdir CLIPPED, "/xl/sharedStrings.xml"
LET plog = pdir CLIPPED, ".log"
# Ejecutables por RUTA COMPLETA: el entorno del runtime de Genero no
# incluye las carpetas de Windows en el PATH, por lo que 'powershell'
# y 'tar' a secas dan "is not recognized". (bs = backslash via ASCII 92,
# porque en fglcomp 3.20 el backslash es caracter de escape.)
LET bs = ASCII 92
LET winroot = fgl_getenv("SystemRoot")
IF winroot IS NULL OR winroot.getLength() = 0 THEN
LET winroot = "C:", bs, "Windows"
END IF
LET psexe = winroot CLIPPED, bs, "System32", bs, "WindowsPowerShell",
bs, "v1.0", bs, "powershell.exe"
LET tarexe = winroot CLIPPED, bs, "System32", bs, "tar.exe"
# borra el log de intentos anteriores
IF os.Path.exists(plog) THEN
LET l_ok = os.Path.delete(plog)
END IF
# 1er intento: PowerShell + .NET ZipFile (funciona en Windows Server 2012+).
# La ruta del .exe NO se encomilla (no tiene espacios); asi el comando no
# empieza con comilla y cmd.exe no elimina las comillas del -Command.
# La salida de error se captura en un log para poder diagnosticar.
LET cmd = psexe CLIPPED, " -NoProfile -ExecutionPolicy Bypass -Command \"",
"Remove-Item -Recurse -Force '", pdir CLIPPED, "' -ErrorAction SilentlyContinue; ",
"Add-Type -AssemblyName System.IO.Compression.FileSystem; ",
"[System.IO.Compression.ZipFile]::ExtractToDirectory('", pfile CLIPPED,
"','", pdir CLIPPED, "')\"",
" 2> \"", plog CLIPPED, "\""
RUN cmd
# 2do intento: tar (Windows 10 / Server 2019+)
IF NOT os.Path.exists(psheet) THEN
IF NOT os.Path.exists(pdir) THEN
LET l_ok = os.Path.mkdir(pdir)
END IF
LET cmd = tarexe CLIPPED, " -xf \"", pfile CLIPPED, "\" -C \"", pdir CLIPPED,
"\" 2>> \"", plog CLIPPED, "\""
RUN cmd
END IF
IF NOT os.Path.exists(psheet) THEN
IF os.Path.exists(plog) THEN
LET g_diag = "No se pudo descomprimir en el servidor.\nDetalle: ",
leer_archivo_texto(plog) CLIPPED
ELSE
LET g_diag = "No se pudo ejecutar la descompresion en el servidor ",
"(PowerShell/tar no disponibles para el usuario del servicio).\nCarpeta: ",
pdir CLIPPED
END IF
RETURN FALSE
END IF
CALL arr_shared.clear()
IF os.Path.exists(pshared) THEN
CALL leer_shared(pshared)
END IF
CALL leer_sheet(psheet)
RETURN TRUE
END FUNCTION
#---------------------------------------------------------------------
# Carga la tabla de cadenas compartidas (sharedStrings.xml)
#---------------------------------------------------------------------
FUNCTION leer_shared(pfile)
DEFINE pfile, content, seg, txt STRING
DEFINE a, b, pos INT
LET content = leer_archivo_texto(pfile)
LET pos = 1
WHILE TRUE
LET a = content.getIndexOf("<si>", pos)
IF a = 0 THEN EXIT WHILE END IF
LET b = content.getIndexOf("</si>", a)
IF b = 0 THEN EXIT WHILE END IF
LET seg = content.subString(a, b+4)
LET txt = extraer_textos_t(seg)
CALL arr_shared.appendElement()
LET arr_shared[arr_shared.getLength()] = txt
LET pos = b + 5
END WHILE
END FUNCTION
#---------------------------------------------------------------------
# Recorre las filas <row> de la hoja
#---------------------------------------------------------------------
FUNCTION leer_sheet(pfile)
DEFINE pfile, content, rowseg STRING
DEFINE rpos, ra, tagend, rb INT
LET content = leer_archivo_texto(pfile)
LET rpos = 1
WHILE TRUE
LET ra = content.getIndexOf("<row", rpos)
IF ra = 0 THEN EXIT WHILE END IF
LET tagend = content.getIndexOf(">", ra)
IF tagend = 0 THEN EXIT WHILE END IF
IF content.getCharAt(tagend-1) = "/" THEN
LET rpos = tagend + 1
CONTINUE WHILE
END IF
LET rb = content.getIndexOf("</row>", tagend)
IF rb = 0 THEN EXIT WHILE END IF
IF rb-1 >= tagend+1 THEN
LET rowseg = content.subString(tagend+1, rb-1)
ELSE
LET rowseg = ""
END IF
CALL procesar_row(rowseg)
LET rpos = rb + 6
END WHILE
END FUNCTION
#---------------------------------------------------------------------
# Procesa las celdas <c> de una fila y las coloca en arr_raw
#---------------------------------------------------------------------
FUNCTION procesar_row(pinner)
DEFINE pinner STRING
DEFINE cpos, ca, tagend, ce, colidx, n INT
DEFINE opentag, inner, letras, tp, val STRING
LET n = arr_raw.getLength() + 1
LET arr_raw[n].c1 = "" LET arr_raw[n].c2 = ""
LET arr_raw[n].c3 = "" LET arr_raw[n].c4 = ""
LET arr_raw[n].c5 = "" LET arr_raw[n].c6 = ""
LET arr_raw[n].c7 = "" LET arr_raw[n].c8 = ""
LET cpos = 1
WHILE TRUE
LET ca = pinner.getIndexOf("<c", cpos)
IF ca = 0 THEN EXIT WHILE END IF
LET tagend = pinner.getIndexOf(">", ca)
IF tagend = 0 THEN EXIT WHILE END IF
LET opentag = pinner.subString(ca, tagend)
IF pinner.getCharAt(tagend-1) = "/" THEN
LET inner = ""
LET cpos = tagend + 1
ELSE
LET ce = pinner.getIndexOf("</c>", tagend)
IF ce = 0 THEN EXIT WHILE END IF
IF ce-1 >= tagend+1 THEN
LET inner = pinner.subString(tagend+1, ce-1)
ELSE
LET inner = ""
END IF
LET cpos = ce + 4
END IF
LET letras = solo_letras(attr_val(opentag, "r"))
LET colidx = letras_a_indice(letras)
LET tp = attr_val(opentag, "t")
LET val = valor_celda(inner, tp)
CASE colidx
WHEN 1 LET arr_raw[n].c1 = val
WHEN 2 LET arr_raw[n].c2 = val
WHEN 3 LET arr_raw[n].c3 = val
WHEN 4 LET arr_raw[n].c4 = val
WHEN 5 LET arr_raw[n].c5 = val
WHEN 6 LET arr_raw[n].c6 = val
WHEN 7 LET arr_raw[n].c7 = val
WHEN 8 LET arr_raw[n].c8 = val
END CASE
END WHILE
END FUNCTION
#---------------------------------------------------------------------
# Devuelve el valor de un atributo (nombre="valor") de una etiqueta
#---------------------------------------------------------------------
FUNCTION attr_val(ptag, pname)
DEFINE ptag, pname, key STRING
DEFINE a, b INT
LET key = pname CLIPPED, "=\""
LET a = ptag.getIndexOf(key, 1)
IF a = 0 THEN RETURN "" END IF
LET a = a + key.getLength()
LET b = ptag.getIndexOf("\"", a)
IF b = 0 OR b-1 < a THEN RETURN "" END IF
RETURN ptag.subString(a, b-1)
END FUNCTION
#---------------------------------------------------------------------
# Deja solo las letras iniciales de una referencia de celda (A12 -> A)
#---------------------------------------------------------------------
FUNCTION solo_letras(pref)
DEFINE pref, res, c STRING
DEFINE i INT
LET res = ""
FOR i = 1 TO pref.getLength()
LET c = pref.getCharAt(i)
IF (c >= "A" AND c <= "Z") OR (c >= "a" AND c <= "z") THEN
LET res = res CLIPPED, c
ELSE
EXIT FOR
END IF
END FOR
RETURN res
END FUNCTION
#---------------------------------------------------------------------
# Convierte letras de columna a indice (A=1, B=2, ... AA=27)
#---------------------------------------------------------------------
FUNCTION letras_a_indice(pletras)
DEFINE pletras, c, abc STRING
DEFINE i, idxc, res INT
LET abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
LET res = 0
LET pletras = upshift(pletras)
FOR i = 1 TO pletras.getLength()
LET c = pletras.getCharAt(i)
LET idxc = abc.getIndexOf(c, 1)
IF idxc = 0 THEN RETURN 0 END IF
LET res = res * 26 + idxc
END FOR
RETURN res
END FUNCTION
#---------------------------------------------------------------------
# Obtiene el valor textual de una celda (resuelve cadenas compartidas)
#---------------------------------------------------------------------
FUNCTION valor_celda(pinner, ptp)
DEFINE pinner, ptp, raw STRING
DEFINE a, gt, b, sidx INT
IF ptp = "inlineStr" THEN
RETURN extraer_textos_t(pinner)
END IF
LET a = pinner.getIndexOf("<v", 1)
IF a = 0 THEN RETURN "" END IF
LET gt = pinner.getIndexOf(">", a)
IF gt = 0 THEN RETURN "" END IF
LET b = pinner.getIndexOf("</v>", gt)
IF b = 0 OR b-1 < gt+1 THEN RETURN "" END IF
LET raw = xml_unescape(pinner.subString(gt+1, b-1))
IF ptp = "s" THEN
LET sidx = raw
IF sidx+1 >= 1 AND sidx+1 <= arr_shared.getLength() THEN
RETURN arr_shared[sidx+1]
END IF
RETURN ""
END IF
RETURN raw
END FUNCTION
#---------------------------------------------------------------------
# Extrae y concatena el texto de todos los nodos <t> de un segmento
#---------------------------------------------------------------------
FUNCTION extraer_textos_t(pseg)
DEFINE pseg, inner STRING
DEFINE sb base.StringBuffer
DEFINE pos, a, gt, b INT
LET sb = base.StringBuffer.create()
LET pos = 1
WHILE TRUE
LET a = pseg.getIndexOf("<t", pos)
IF a = 0 THEN EXIT WHILE END IF
LET gt = pseg.getIndexOf(">", a)
IF gt = 0 THEN EXIT WHILE END IF
IF pseg.getCharAt(gt-1) = "/" THEN
LET pos = gt + 1
CONTINUE WHILE
END IF
LET b = pseg.getIndexOf("</t>", gt)
IF b = 0 THEN EXIT WHILE END IF
IF b-1 >= gt+1 THEN
LET inner = pseg.subString(gt+1, b-1)
ELSE
LET inner = ""
END IF
CALL sb.append(xml_unescape(inner))
LET pos = b + 4
END WHILE
RETURN sb.toString()
END FUNCTION
#---------------------------------------------------------------------
# Desescapa entidades XML basicas
#---------------------------------------------------------------------
FUNCTION xml_unescape(ps)
DEFINE ps, s STRING
LET s = ps
LET s = replace_str(s, "&lt;", "<")
LET s = replace_str(s, "&gt;", ">")
LET s = replace_str(s, "&quot;", "\"")
LET s = replace_str(s, "&apos;", "'")
LET s = replace_str(s, "&amp;", "&")
RETURN s
END FUNCTION
#---------------------------------------------------------------------
# Reemplaza todas las ocurrencias de una subcadena
#---------------------------------------------------------------------
FUNCTION replace_str(ps, po, pn)
DEFINE ps, po, pn STRING
DEFINE sb base.StringBuffer
LET sb = base.StringBuffer.create()
CALL sb.append(ps)
CALL sb.replace(po, pn, 0)
RETURN sb.toString()
END FUNCTION
#---------------------------------------------------------------------
# Lee un archivo de texto completo a una cadena
#---------------------------------------------------------------------
FUNCTION leer_archivo_texto(pfile)
DEFINE pfile, linea STRING
DEFINE ch base.Channel
DEFINE sb base.StringBuffer
LET sb = base.StringBuffer.create()
LET ch = base.Channel.create()
CALL ch.openFile(pfile, "r")
WHILE NOT ch.isEof()
LET linea = ch.readLine()
IF linea IS NULL THEN EXIT WHILE END IF
CALL sb.append(linea)
CALL sb.append(" ")
END WHILE
CALL ch.close()
RETURN sb.toString()
END FUNCTION
#---------------------------------------------------------------------
# Valida / normaliza arr_raw y llena arr_ofertas
#---------------------------------------------------------------------
FUNCTION procesar_raw()
DEFINE i, k, errores, sinprod, sinfecha INT
DEFINE vcodn, vcodgrupo, vcodtipo, vcodsec INT
DEFINE vporc DEC(12,2)
DEFINE vfini, vffin DATE
DEFINE vdesc VARCHAR(100)
DEFINE vunidad VARCHAR(4)
DEFINE msgtxt STRING
CALL arr_ofertas.clear()
LET k = 0
LET errores = 0
LET sinprod = 0
LET sinfecha = 0
FOR i = 1 TO arr_raw.getLength()
# salta encabezados / titulos / filas sin codigos validos
IF NOT es_entero(arr_raw[i].c1) OR NOT es_entero(arr_raw[i].c2)
OR NOT es_entero(arr_raw[i].c3) OR NOT es_entero(arr_raw[i].c4) THEN
CONTINUE FOR
END IF
LET vcodn = arr_raw[i].c1
LET vcodgrupo = arr_raw[i].c2
LET vcodtipo = arr_raw[i].c3
LET vcodsec = arr_raw[i].c4
# el producto debe existir y estar activo
LET vdesc = NULL
LET vunidad = NULL
SELECT a.descrip_esp, a.unidad_med INTO vdesc, vunidad
FROM iptb00002 a
WHERE a.cod_n = vcodn AND a.cod_grupo = vcodgrupo
AND a.cod_tipo = vcodtipo AND a.cod_sec = vcodsec
AND a.status_t IS NULL
IF vdesc IS NULL THEN
LET sinprod = sinprod + 1
LET errores = errores + 1
CONTINUE FOR
END IF
# porciento de descuento
LET vporc = normaliza_porc(arr_raw[i].c5)
IF vporc IS NULL OR vporc <= 0 THEN
LET errores = errores + 1
CONTINUE FOR
END IF
# fechas
LET vfini = parse_fecha(arr_raw[i].c6)
IF vfini IS NULL THEN
LET vfini = TODAY
END IF
LET vffin = parse_fecha(arr_raw[i].c7)
IF vffin IS NULL THEN
LET sinfecha = sinfecha + 1
LET errores = errores + 1
CONTINUE FOR
END IF
IF vffin < vfini THEN
LET errores = errores + 1
CONTINUE FOR
END IF
LET k = k + 1
LET arr_ofertas[k].id = k
LET arr_ofertas[k].cod_n = vcodn
LET arr_ofertas[k].cod_grupo = vcodgrupo
LET arr_ofertas[k].cod_tipo = vcodtipo
LET arr_ofertas[k].cod_sec = vcodsec
LET arr_ofertas[k].descripcion = vdesc
LET arr_ofertas[k].unidad_med = vunidad
LET arr_ofertas[k].porc_desc = vporc
LET arr_ofertas[k].fecha_inicial = vfini
LET arr_ofertas[k].fecha_final = vffin
END FOR
LET no_registros = arr_ofertas.getLength()
DISPLAY BY NAME no_registros
# el documento requiere fecha (NOT NULL); si el flujo de carga no la fijo,
# se asume la fecha de hoy y se muestra al usuario
IF datos_c.fecha IS NULL THEN
LET datos_c.fecha = TODAY
DISPLAY BY NAME datos_c.fecha
END IF
LET msgtxt = "Se cargaron ", k USING "<<<<&", " ofertas."
IF errores > 0 THEN
LET msgtxt = msgtxt CLIPPED, "\n", errores USING "<<<<&",
" filas omitidas (", sinprod USING "<<<<&",
" sin producto activo, ", sinfecha USING "<<<<&",
" sin fecha final)."
END IF
IF k = 0 THEN
CALL fgl_winmessage("IMPORTAR", msgtxt, "exclamation")
ELSE
LET msgtxt = msgtxt CLIPPED,
"\nRevise el detalle y presione GUARDAR para aplicar."
CALL fgl_winmessage("IMPORTAR", msgtxt, "information")
END IF
END FUNCTION
#---------------------------------------------------------------------
# Normaliza el % de descuento: valores <= 1 se toman como fraccion x100
#---------------------------------------------------------------------
FUNCTION normaliza_porc(ps)
DEFINE ps, s STRING
DEFINE v DEC(12,2)
LET s = ps.trim()
IF s.getLength() = 0 THEN RETURN NULL END IF
IF NOT es_numero(s) THEN RETURN NULL END IF
LET v = s
IF v > 0 AND v < 1 THEN
LET v = v * 100
END IF
RETURN v
END FUNCTION
#---------------------------------------------------------------------
# Interpreta una fecha: serial de Excel, AAAA-MM-DD o DD/MM/AAAA
#---------------------------------------------------------------------
FUNCTION parse_fecha(ps)
DEFINE ps, s, p1, p2, p3 STRING
DEFINE serial, y, m, d, cnt INT
DEFINE tok base.StringTokenizer
LET s = ps.trim()
IF s.getLength() = 0 THEN RETURN NULL END IF
# numero serial de Excel (dias desde 1899-12-30)
IF es_numero(s) THEN
IF s.getIndexOf(".",1) > 0 THEN
LET s = s.subString(1, s.getIndexOf(".",1)-1)
END IF
IF s.getLength() = 0 THEN RETURN NULL END IF
LET serial = s
IF serial <= 0 THEN RETURN NULL END IF
RETURN MDY(12,30,1899) + serial
END IF
LET tok = base.StringTokenizer.create(s, "-/.")
LET cnt = 0
IF tok.hasMoreTokens() THEN LET p1 = tok.nextToken() LET cnt = cnt + 1 END IF
IF tok.hasMoreTokens() THEN LET p2 = tok.nextToken() LET cnt = cnt + 1 END IF
IF tok.hasMoreTokens() THEN LET p3 = tok.nextToken() LET cnt = cnt + 1 END IF
IF cnt < 3 THEN RETURN NULL END IF
IF NOT es_entero(p1) OR NOT es_entero(p2) OR NOT es_entero(p3) THEN
RETURN NULL
END IF
IF p1.getLength() = 4 THEN # ISO AAAA-MM-DD
LET y = p1 LET m = p2 LET d = p3
ELSE # R.D. DD/MM/AAAA
LET d = p1 LET m = p2 LET y = p3
IF p3.getLength() = 2 THEN
LET y = 2000 + y
END IF
END IF
# tolera hojas guardadas en formato MM/DD/AAAA
IF m > 12 AND d <= 12 THEN
LET cnt = m LET m = d LET d = cnt
END IF
IF m < 1 OR m > 12 OR d < 1 OR d > 31 THEN RETURN NULL END IF
RETURN MDY(m, d, y)
END FUNCTION
#---------------------------------------------------------------------
# TRUE si la cadena contiene solo digitos (entero no negativo)
#---------------------------------------------------------------------
FUNCTION es_entero(ps)
DEFINE ps, s, c STRING
DEFINE i INT
LET s = ps.trim()
IF s.getLength() = 0 THEN RETURN FALSE END IF
FOR i = 1 TO s.getLength()
LET c = s.getCharAt(i)
IF c < "0" OR c > "9" THEN RETURN FALSE END IF
END FOR
RETURN TRUE
END FUNCTION
#---------------------------------------------------------------------
# TRUE si la cadena es un numero (digitos y a lo sumo un punto)
#---------------------------------------------------------------------
FUNCTION es_numero(ps)
DEFINE ps, s, c STRING
DEFINE i, puntos INT
LET s = ps.trim()
IF s.getLength() = 0 THEN RETURN FALSE END IF
LET puntos = 0
FOR i = 1 TO s.getLength()
LET c = s.getCharAt(i)
IF c = "." THEN
LET puntos = puntos + 1
IF puntos > 1 THEN RETURN FALSE END IF
ELSE
IF c < "0" OR c > "9" THEN RETURN FALSE END IF
END IF
END FOR
RETURN TRUE
END FUNCTION