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("", pos) IF a = 0 THEN EXIT WHILE END IF LET b = content.getIndexOf("", 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 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("", 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("", 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 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("", 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("", 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("", a) IF gt = 0 THEN RETURN "" END IF LET b = pinner.getIndexOf("", 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 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("", 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("", 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, "<", "<") LET s = replace_str(s, ">", ">") LET s = replace_str(s, """, "\"") LET s = replace_str(s, "'", "'") LET s = replace_str(s, "&", "&") 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