viernes, 7 de junio de 2024

ODOO (XVII) Crear un servicio para montar una carpeta con Samba de un servidor Windows

 1. Crear el punto de montaje

Primeramente crearemos la carpeta /mnt/ZZ_SCANEIG en nuestro serviodor de ODOO en Debian


2. Crear el shell script de montaje

Ahora crearemos en /home/informatica/MyShells este script

#1. j = Número de parametros que damos de entrada
j=$#;

#2. Damos una clave de desencriptación mala
myPwd='....'
#3. Solo asumimos un parámetro de entrada que es la clave para desencriptar
if [ $j -eq 1 ]
then 
  myPwd=$1
fi

#4. Definimos las carpetas de montaje y de linkado externo 
mntFolders[0]=/mnt/ZZ_SCANEIG
folders[ 0]=//192.168.28.6/S$/E2-062_URBANISME/ZZ-ESCANEIG
#5. Usuario y contraseñas encriptados de acceso al servidor de disco Windows
usersencr[ 0]='U2FsdGVkX1+7KTYaMPWpBTyzfM60e3VEw3U+OedTXQs='
pwdsencr[ 0]='U2FsdGVkX1+iS2UfAhfIyTabgUjJ9ws1jkU9lA2sxBw='

#6. Numero de carpetas a montar (en este caso solo una
total=${#mntFolders[*]}
for (( i=0; i<=$(( $total -1 )); i++ ))
do
    echo "$i montant en ${mntFolders[$i]} a  ${folders[$i]}"

    #7. Desencriptando usuario y contraseña con la clave
    aPwd=`echo "${pwdsencr[$i]}" | openssl enc -aes-256-cbc -md sha512 -a -d -pbkdf2 -iter 100000  -salt -pass pass:"$myPwd"`
    aUser=`echo "${usersencr[$i]}" | openssl enc -aes-256-cbc -md sha512 -a -d -pbkdf2 -iter 100000  -salt -pass pass:"$myPwd"`

    #8. MOntando la carpeta en el punto de linkado con Samba v.3
echo "$pwdEdu" | sudo -S mount -t cifs -o "domain=AYUNTAMIENTO,username=$aUser,password=$aPwd,vers=3.0,rw,hard,nosetuids,noperm" "${folders[$i]}" "${mntFolders[$i]}"
done


3. Crear el fichero de servicio systemd

Nos vasamos en https://www.heatware.net/linux-tips/run-execute-script-on-linux-startup/. Ahora ejecutamos

# Creación del fichero de servicio
sudo nano /etc/systemd/system/mount_disks.service

Y escribimos

[Unit]
Description=Mount ZZ_ESCNEIG disk at startup

[Service]
ExecStart=/home/informatica/MyShells/mount_disks.sh

[Install]
WantedBy=multi-user.target


4. Activar y arrancar el servicio

Ejecutamos

# Activar el servicio
sudo systemctl enable your_service.service

# Arrancar el servicio
sudo systemctl start mount_disks.service

jueves, 6 de junio de 2024

UI Vision RPA . Un ejemplo de captura de datos desktop windows

 1. Instalar la extensión UI Vision en Chrome, y ejecutarla

Ir a la URL de Exteniones Chrome  y buscar "UI Vision"


Una vez se instala aparece el icono de uivision, haciendo doble click sobre el se ejecuta

Y se abre la aplicación


2. Configurar la aplicación

Le damos al icono de configuración


2.1 Seleccionar el tipo de aplicación desktop o Browser

Vamos a la pestaña Vision. En este caso empezamos con una aplicación tipo desktop. En Linux hay que tener cuidado pues el capturador de imagenes es Shutter y parece ser que la version de Shutter para las últimas versiones de Ubuntu no funciona bien. 

Es importante establecer el parametro wait alrededor de 3 segundos o tal vez un poco mas para que de tiempo de seleccionar opciones de las listas desplegables




Se debe de instalar el Screen Capture Module

Para instalarlo en windows requiere que se haga como administrador

2.2 Configurar donde se guardan los ficheros

En la pestaña XModules, inidcar el Home Folder


Salimos de Settings y en Storage Mode elegimos File System (on hard Drive) así tenemos más control de los scripts.



2.3 Generar el fichero autostart

Vamos a la pestaña API. Este fichero es muy importante si queremos ejecutar las macros de forma desantedida con un shell script. Para ello vamos a API y Generate Autsctart HTML y la guardamos en una carpeta nueva que en my caso llamo MyShells


3. Comandos básicos

3.1 XType: Teclear

   {
      "Command": "XType",
      "Target": "${KEY_WIN+KEY_HOME}",
      "Value": "",
      "Description": "Amb Win-Home minimitzem la resta de finestres"
    }

Para el caso de combinación de teclas especiales

Ejemplo 1: Enviar Windows-Home para minimizar todas las pantallas menos la actual. En target indicar:

${KEY_WIN+KEY_HOME} 

Ejemplo 2: Enviar texto. En target coloacar el texto como Hola 


3.2 pause: Esperar milisegundos

   {
      "Command": "pause",
      "Target": "6000",
      "Value": "",
      "Description": "Esperem 6 segons a que es carregue l'aplicació"
    }

Esperamos a que se ejecute algo 


3.3 XClick: Dar click o doble click sobre una imagen   

      "Command": "XClick",
      "Target": "certificatedu5_dpi_96.png",
      "Value": "#doubleclick",
      "Description": "Fem doble click sobre el certificat"
    }

Hacemos click (Value="") o doble click (Value="#doubleclick") sobre una imagen que hemos capturado y guardado. Para capturar la imagen hay que  darle al botón "Select" en el Target


3.4 XClickRelative: Dar click o doble click sobre una parte de una imagen 

      "Command": "XClickRelative",
      "Target": "certificatedu5_dpi_96.png",
      "Value": "#doubleclick",
      "Description": "Fem doble click sobre el certificat"
    }

Hacemos click (Value="") o doble click (Value="#doubleclick") sobre una imagen que hemos capturado y guardado. Para capurar la imagen hay que  darle al botón "Select" en el Target.

Pero tenemos que definir las zonas de búsqueda y de click

Para ello buscamos el fichero que hemnos guardado y lo buscamos en la parte inferior (en este caso el fichero lo guardamos como "NomExcel_dpi_96.png") Basta con buscar por "Nom_Excel",

Aparecerá un poco más abajo y le damos al icono del ojo


Aparece una especie de editor donde le marcamos con un cuadro verde una subimagen de la imagen que nos servira de referencia para localizar la zona donde tenemos que hacer el click que marcaremos con un cuadro rosa. Hay que tener paciencioa pues le cuesta seleccionar las zonas y hay que probar varias veces.


4. Escript generado para descargar una excel del padrón de habitantes

{
  "Name": "Excel_Padro2",
  "CreationDate": "2024-6-6",
  "Commands": [
    {
      "Command": "XType",
      "Target": "${KEY_WIN+KEY_HOME}",
      "Value": "",
      "Description": "Amb Win-Home minimitzem la resta de finestres"
    },
    {
      "Command": "XType",
      "Target": "${KEY_WIN+KEY_M}",
      "Value": "",
      "Description": "Amb Win-M minimitzem totes finestres"
    },
    {
      "Command": "XClick",
      "Target": "Poblacion_Icon_dpi_96.png",
      "Value": "#doubleclick",
      "Description": "Escriptori-Icono Poblacion-2Clck"
    },
    {
      "Command": "pause",
      "Target": "7000",
      "Value": "",
      "Description": "Esperar Carregar App Interpublica"
    },
    {
      "Command": "XClick",
      "Target": "Interpublica22_dpi_96.png",
      "Value": "#doubleclick",
      "Description": "Capçalera Pantalla Certificat-2Click"
    },
    {
      "Command": "XClick",
      "Target": "certificatedu5_dpi_96.png",
      "Value": "#doubleclick",
      "Description": "Certificat Edu-2Clck"
    },
    {
      "Command": "pause",
      "Target": "6000",
      "Value": "",
      "Description": "Esperar càrrega App Padró"
    },
    {
      "Command": "XClick",
      "Target": "Tancar_dpi_96.png",
      "Value": "#doubleclick",
      "Description": "Tancar missatge informatiu certificats"
    },
    {
      "Command": "XClick",
      "Target": "Poblacio2_dpi_96.png",
      "Value": "#doubleclick",
      "Description": "Poblacio Icona-2Click"
    },
    {
      "Command": "XClick",
      "Target": "Tancar_dpi_96.png",
      "Value": "#doubleClick",
      "Description": "Tancar missatge informatiu certificats altra vegada"
    },
    {
      "Command": "XClick",
      "Target": "documentsllistes2_dpi_96.png",
      "Value": "#doubleclick",
      "Description": "Documents i LListes-2Click"
    },
    {
      "Command": "XClick",
      "Target": "DocumentsILlistesDropDown_dpi_96.png",
      "Value": "#doubleclick",
      "Description": "documents i llistes DROPDOWN- Click"
    },
    {
      "Command": "XClick",
      "Target": "GeneradorDeLlistats_dpi_96.png",
      "Value": "",
      "Description": "Generador de llistats-Click"
    },
    {
      "Command": "XClickRelative",
      "Target": "Filtres_Nom_dpi_96.png",
      "Value": "",
      "Description": "Localitzar Camp Nom de filtres-ClickRelaticve"
    },
    {
      "Command": "XType",
      "Target": "EDU:Padro Rolan",
      "Value": "",
      "Description": "Escrivim EDU:Padro Rolan"
    },
    {
      "Command": "XClick",
      "Target": "Lupa_dpi_96.png",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "XClick",
      "Target": "EDU_Padron_dpi_96.png",
      "Value": "",
      "Description": "Llistat EDU_PADRO..-Click"
    },
    {
      "Command": "XClick",
      "Target": "Veure_dpi_96.png",
      "Value": "",
      "Description": "Veure-Click"
    },
    {
      "Command": "XClick",
      "Target": "Acceptar_dpi_96.png",
      "Value": "",
      "Description": "Boto Acceptar-Click"
    },
    {
      "Command": "pause",
      "Target": "30000",
      "Value": "",
      "Description": "Esperar que es carregue Open Office"
    },
    {
      "Command": "XClick",
      "Target": "Archivo_dpi_96.png",
      "Value": "",
      "Description": "Menu-Archivo-Click"
    },
    {
      "Command": "XClick",
      "Target": "Guardar_Como_dpi_96.png",
      "Value": "",
      "Description": "Drop Down Guardar Como-Click"
    },
    {
      "Command": "XType",
      "Target": "Z:\\A0-003_INFORMATICA\\ASS\\ASS2024\\AS2024-0042_Padro_habitants_policia\\PH_ROLAN_ODOO.xlsx",
      "Value": "",
      "Description": "Esborra nom excel proposat"
    },
    {
      "Command": "XType",
      "Target": "${KEY_DEL}",
      "Value": "",
      "Description": "Nom de Full Excel Exportat"
    },
    {
      "Command": "XClick",
      "Target": "Guardar_dpi_96.png",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "XClick",
      "Target": "Si_dpi_96.png",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "pause",
      "Target": "10000",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "XClick",
      "Target": "X_dpi_96.png",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "pause",
      "Target": "3001",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "XClick",
      "Target": "Tancar1_dpi_96.png",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "XClick",
      "Target": "eixirInterpublica_dpi_96.png",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "XClick",
      "Target": "eixirIconInterpublica_dpi_96.png",
      "Value": "",
      "Description": ""
    }
  ]
}


5. Ejecutar el programa con el asistente

Se puedeejecutar paso a paso (Step) o todo (Play Macro)


6.  Ejecutar como un shell script

Tal como se explica en las instrucciones hay que ejecutar el navegador al que le pasamos la ruta del fichero generado en el punto 2.1 y la ruta de la macro.

En Windows tenemos:

  1. Ruta de chrome: "C:\Program Files\Google\Chrome\Application\chrome.exe"
  2. Ruta del fichero: En mi caso: C:/Users/eduard/Desktop/uivision/MyHtml/ui.vision.html
  3. Nombre macro: En mi caso: Excel_Padro2
  4. Fichero log: En mi caso: =C:/Users/eduard/Desktop/uivision/MyLogs/log1.txt
Para que se cierre el Chrome automáticamente hay que hacer lo que dice Plankton que es añadir al final de la orden  &closeRPA=1 &closeBrowser=1"

El fichero batch en Windows será "ExecuteExcel_Padro2.bat" que lo hemos creado dentro "Desktop/uivision/MyShells"

REM Executem el UI.VISION RPA macro Excel_Padro2
"C:\Program Files\Google\Chrome\Application\chrome.exe" "file:///C:/Users/eduard/Desktop/uivision/MyHtml/ui.vision.html?direct=1&macro=Excel_Padro2&savelog=C:/Users/eduard/Desktop/uivision/MyLogs/log1.txt&closeRPA=1&closeBrowser=1"

Para que se va mas claro:

REM Executem el UI.VISION RPA macro Excel_Padro2
"C:\Program Files\Google\Chrome\Application\chrome.exe" 
  "file:///C:/Users/eduard/Desktop/uivision/MyHtml/ui.vision.html
    ?direct=1
    &macro=Excel_Padro2
    &savelog=C:/Users/eduard/Desktop/uivision/MyLogs/log1.txt
    &closeRPA=1
    &closeBrowser=1
  "


Ejecutamos y nos sale un error


Vamos a "Settings" o Ajunstes de Chrome con los 3 puntitos verticales a al parte superior derecha. Después vamos a "Extensions" en el menu de la izquierda. Apretamos el botón de "Detalles" de UI.Vision.RPS y activamos "Allow access to file URLs"


Además pueden salir mas errores. Hay un error que me sale que es que un botón a veces cambia de color pasando de un gris azulado a azul oscuro. Cuando intento coger la imagen del botón mediante el UI Vision, entonces cambia de color. Para ello utilizo otro capturador de imágen y guardo la imagen y luego la importo. Y en la macro le digo que haga click tanto en la imagen azul oscura como el la gris azulada.

7.  Ejecutar como un shell script todos los dias

Se configurará con tareas programadas

Vamos a tareas programas de Wiondows y Creamos una nueva carpeta haciendo click rerecho sobre "Biblioteca del Programador de tareas" y creamos una carpeta llamada EDU


Dentro de esa carpeta creamos otra que se llama EDU-Captura-PADRON de la misma manera que hemos operado antes


Seleccionamos esa carpeta y ya le damos a Crear tarea básica



    Le damos nombre y descripción


Le damos siguiente y le decimos que se ejecute diariamente y siguiente y le damos las hora y siguiente

En acción le decimos que inicie un programa
Y seleccionamos el programa


Le damos a finalizar y en la pestaña de mostrar propiedades le decimos que si.

Y hay que decirle que se ejecute solo si el usuario ha iniciado la sesión, pues en caso contrario jecuta elproceso en background y nuestri RPA no funciona bien pues no tiene pantallas donde trabajar




Veamos el resto de pantallas per hay que comprobar que acción se desencadena



Y también debemos controlar si dura más de la cuenta y finalizarla si tarda mas de una hora en este caso.


Ahora ya la tenemos en dicha carpeta 


Y si queremos ejecutarla, hacemos click derecho sobre la tarea y la ejecutamos.





8. Anexo


Para que se liste las tareas programadas, se ejecuta:

Schtasks /Query /FO LIST /V > mytasks.txt

y al mismo tiempo se guarda en el fichero "mytasks.txt" pues hay muchas










 















jueves, 25 de abril de 2024

ODOO (XVI) Consultar la base de datos Postgres

 Vemos algunas sentencias para descubrir la información de la provincia de Valencia.


Para ello entramos en Odoo y seleccionameos en Configuracion el modo desarrollador y comprobamos que aparece el escarabajo.

Entramos en el menu Contactos - Configuracion - (Localizacion Provincias)

Ahora le damos al Escarabajo - Ver campos 

y sale que la el modelo es Country State que es res.country.state

por tanto hay que buscar una tabla que se llame parecido (seguramene sera res_country_state


# 1. Conectarse como postgres
myuser@SRV-ODOO-ERP:~$ sudo -i -u postgres

# 2. pide la contraseña (en principio de su)

# 3.ejecutar psql
postgres@SRV-ODOO-ERP:~$ psql 

# 4. Listr las pases de datos
postgres=# \l

# 5. Devuelve
#   Name    |  Owner   | Encoding |   Collate   |    Ctype    | ICU Locale | Locale Provider |   Access privileges   
#-----------+----------+----------+-------------+-------------+------------+-----------------+-----------------------
# odoo      | odoo     | UTF8     | C           | es_ES.UTF-8 |            | libc            | 
# postgres  | postgres | UTF8     | es_ES.UTF-8 | es_ES.UTF-8 |            | libc            | 
# template0 | postgres | UTF8     | es_ES.UTF-8 | es_ES.UTF-8 |            | libc            | =c/postgres          +
#           |          |          |             |             |            |                 | postgres=CTc/postgres
# template1 | postgres | UTF8     | es_ES.UTF-8 | es_ES.UTF-8 |            | libc            | =c/postgres          +
#           |          |          |             |             |            |                 | postgres=CTc/postgres
#(4 rows)

# 5. Nos salimos
q

# 6. Nos conectamos a la bbdd "odoo"
postgres=# \c odoo
# You are now connected to database "odoo" as user "postgres # 7. Comados para listar # 7.1 \d lista todo todo # 7.2 \dn lista esquems # 7.3 \dt lista tablas # 8. mostrar las tablas y vemos que aprece una llamada res_country_state odoo=# \dt # 9. Ver los campos de una tabla (No olvidar el ";" del final !!! odoo=# select column_name from information_schema.columns where table_name='res_country_state'; # column_name #------------- # id # country_id # create_uid # write_uid # create_date # write_date # name # code #(8 rows) # 10. Ver la información de la provincia de Valencia odoo=# select id, name, code, country_id from res_country_state where code='V'; # id | name | code | country_id #-----+---------------------+------+------------ # 464 | València (Valencia) | V | 68 # 575 | Tierra del Fuego | V | 10 #(2 rows)


En este caso consultamos el valor del id y del country_id


ODOO (XV) Añadir campos al modelo

0. Introducción

Créditos: RGB Consulting 

1. Añadir un campo

Entrar en el boton de la parte superior izquierda(1) -Ajustes (2) -Técnico(3)  -Estructura de la base de datos (4) - Modelo (5)



Ahora buscamos el modelo que es hr.employee en el filtro 




Una vez dentro del modelo, nos vamos a la ventana de campos (1) y paginamos hasta el final (2) (en mi caso hay que correr 4 pantallas, y al seleccionamos agregar línea (3). 

OJO: Al paginar hay que paginar sobre la ventana de campos del modelo que está un poco mas abajo, y no en la ventana de modelos que está arriba!!

Ahora añadimos el campo x_micampo y le indicamos el tipo de campo, y varias características mas.




2. Crear un formulario donde aparezca dicho campo

Entrar en el boton de la parte superior izquierda(1) -Ajustes (2) -Técnico(3)  -Interfaz de Usuario (4) - Vistas (5) tal como hemos visto en el apartado anterior hasta el punto 3











martes, 23 de abril de 2024

Python (III). Crear ejecutables linux y Windows

0. Introducción

Si estamos en Linux solo podremos hacer ejecutables windows si utilizamos Wine, pero suele fallar.

Para ello es conveniente utilizar Windows para realizar ejecutables Windows y Linux para hacer ejecutables Linux.

Lo primero que hay que hacer es instalar pyinstaller

# En windows
pip install pyinstaller
# En windows si falla
python -m pip install pyinstaller
# En Linux ./bin/python -m pip install pyinstaller

1. Caso especial de windows 11

Cuando se instala en windows 11 se instala pyinstaller dentro de la carpeta del usuario. Por tanto debemos incluir en el path del usuario 

En windows tenemos que añadir a las variables de entorno de usuario estas carpetas tal como se muestra:

C:\Users\eduard\AppData\Local\Programs\Python\Python312\Scripts

C:\Users\eduard\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0\LocalCache\local-packages\Python312\Scripts






2. Generación del ejecutable

Ahora ejecutamos la opción para que genere un único fichero (en este caso hemos utilizado el módulo python mdph.py


# En windows
Pyinstaller --onefile -w mdph.py
# En windows, si falla
python -m Pyinstaller --onefile -w mdph.py

# En Linux
pyinstaller --onefile mdph.py

Ahora ejecutamos la opción para que genere un único fichero

Para el caso de linux, crea una carpeta llamada dist y dentro está el ejecutable mdph


3. Ejemplo de conversión

Anteriormente hemos utilizado el comando de creación del ejecutable del módulo mdph.py. Ahora vamos a mostrar el detalle de este fichero y el módulo adicional que utiliza.

El problema en cuestión consiste en transformar un fichero excel que por cada persona tiene 4 filas, donde hay un campo "Denominació" que puede tomar los valores "Observacions, pares, emails y telefons", y otro campo "Valor" que toma los valores específicos". El fichero transformado tendrá un único registro por persona y le añadirá los 4 campos Observacions, pares, emails y telefons"

Para este ejemplo tenemos que instalar las dependencias pandas, numpy y tkinter


# En windows
pip install pandas
pip install numpy
pip install tkinter
# En Linux ./bin/python -m pip install pandas
./bin/python -m pip install numpy
./bin/python -m pip install tkinter



Y utilizamos 2 módulos mdexcel.py y mdph.py

El primero (mdexcel.py) tiene utilidades para manejar ficheros excel
El segundo (mdph.py)

3.1 mdexcel.py



import
pandas import numpy def getExcel(object)->object: excel=pandas.read_excel(object) return excel def getCSVText(object)->str: myStr="" excel=pandas.ExcelFile(object) for sheetName in excel.sheet_names: sheet=pandas.read_excel(excel, sheetName) myStr+=str(sheet) print (myStr) return myStr #getCSVText("/home/eduard/Baixades/edu.xlsx") def getColumnNamesAndRows(obj:object, sheetName:str='')->list[list[object]]: excel=pandas.ExcelFile(obj) print (excel) colNames=[] if len(sheetName)==0: sheet=pandas.read_excel(excel, excel.sheet_names[0]) else: sheet=pandas.read_excel(excel, excel.sheet_names[excel.sheet_names.index(sheetName)]) for column in sheet.columns: print (column) colNames.append(column) return colNames,sheet.values def getColumnValues(columnNames:list[str], values:list[list[object]], wantedCols:list[str])->list[object]: allCols=[] for colName in wantedCols: print('colName='+colName) myCol=[] colNum=columnNames.index(colName) for row in values: myValue=row[colNum] myCol.append(myValue) allCols.append(myCol) return allCols def list2ExcelFile(inList:list[list[object]], filePath:str, transpose:bool=False): df=None if transpose: print('Transposing the matrix and getting dataframe........') df=pandas.DataFrame(numpy.array(inList).T) else: print('Getting dataframe........') df=pandas.DataFrame(inList) with pandas.ExcelWriter(filePath) as writer: print ('Writing excel File.....') df.to_excel(writer, sheet_name='Full1')



3.2 mdph.py


import mdexcel
import tkinter
import tkinter.filedialog

#-------------------------------------------------------------------
#  Arreglem el xls que torna el programa del padró que conté fins 
#  4 registres per persona amb cada línia té: 
#  pares, observacions, teléfons i emails
#  I afegeix eixos 4 camps i deixa només una línia per persona
#-------------------------------------------------------------------
def arreglarXLS(input:object, outFilePath:str)->object:
    
    print('1. Reading Excel File...')
    columnNames,myData= mdexcel.getColumnNamesAndRows(input)
    
    myWantedCols=['Nº documento','Nom'           ,'Partícula 1'  ,'Cognom 1'             ,'Partícula 2',
                  'Cognom 2'    ,'Data naixement','Cognoms i Nom','Tipus Via normalitzat','Nom Via',
                  'Nombres'     ,'Escala'        ,'Codi Planta'  ,'Porta'                ,'Nom nucli/disseminat (50)',
                  'Adreça'      ,"Data d'alta"   ,'Denominació'  ,'Valor'                ,'Estat']
    myColNames=     myWantedCols[0:17] + ['Emails','Observacions','Telefons', 'PARES'] + myWantedCols[19:20]
    
    print('2. getting column Values...')
    myDataCols=mdexcel.getColumnValues(columnNames, myData, myWantedCols)
    
    print('3. Selecting data...')
    myDocu='xxxx',
    myCognomNom='xxxxx'
    #Create a list of columns
    myNewDataCols=[]
    #add column headers
    for name in myColNames:
        aList=[]
        aList.append(name)
        myNewDataCols.append(aList)
    ii=0
    for i in range(len(myDataCols[0])):
        if myDataCols[0][i]!=myDocu or myDataCols[7][i]!=myCognomNom:
            myDocu=     myDataCols[0][i]
            myCognomNom=myDataCols[7][i]
            ii+=1
            for j in range(len(myColNames)):
                myNewDataCols[ j].append('')
                if j<17 :                     myNewDataCols[ j][ii]=myDataCols[j][i]
                if j>=17 and j<21:            myNewDataCols[ j][ii]=''
                if j==21:                     myNewDataCols[ j][ii]=myDataCols[19][i]

        if myDataCols[17][i]==myColNames[17]: myNewDataCols[17][ii]=myDataCols[18][i]    
        if myDataCols[17][i]==myColNames[18]: myNewDataCols[18][ii]=myDataCols[18][i]    
        if myDataCols[17][i]==myColNames[19]: myNewDataCols[19][ii]=myDataCols[18][i]    
        if myDataCols[17][i]==myColNames[20]: myNewDataCols[20][ii]=myDataCols[18][i]    

    print('4. Writing excel file...')
    mdexcel.list2ExcelFile(myNewDataCols,outFilePath, transpose=True)       

    print('5. The end ...')
    return True


def getOutputFileName(inputFileName:str)->str:
    if inputFileName.endswith('.xlsx'):
        return inputFileName.replace('.xlsx','.arreglat.xlsx')
    else: import mdexcel
import tkinter

#-------------------------------------------------------------------
#  Arreglem el xls que torna el programa del padró que conté fins 
#  4 registres per persona amb cada línia té: 
#  pares, observacions, teléfons i emails
#  I afegeix eixos 4 camps i deixa només una línia per persona
#-------------------------------------------------------------------
def arreglarXLS(input:object, outFilePath:str)->object:
    
    print('1. Reading Excel File...')
    columnNames,myData= mdexcel.getColumnNamesAndRows(input)
    
    myWantedCols=['Nº documento','Nom'           ,'Partícula 1'  ,'Cognom 1'             ,'Partícula 2',
                  'Cognom 2'    ,'Data naixement','Cognoms i Nom','Tipus Via normalitzat','Nom Via',
                  'Nombres'     ,'Escala'        ,'Codi Planta'  ,'Porta'                ,'Nom nucli/disseminat (50)',
                  'Adreça'      ,"Data d'alta"   ,'Denominació'  ,'Valor'                ,'Estat']
    myColNames=     myWantedCols[0:17] + ['Emails','Observacions','Telefons', 'PARES'] + myWantedCols[19:20]
    
    print('2. getting column Values...')
    myDataCols=mdexcel.getColumnValues(columnNames, myData, myWantedCols)
    
    print('3. Selecting data...')
    myDocu='xxxx',
    myCognomNom='xxxxx'
    #Create a list of columns
    myNewDataCols=[]
    #add column headers
    for name in myColNames:
        aList=[]
        aList.append(name)
        myNewDataCols.append(aList)
    ii=0
    for i in range(len(myDataCols[0])):
        if myDataCols[0][i]!=myDocu or myDataCols[7][i]!=myCognomNom:
            myDocu=     myDataCols[0][i]
            myCognomNom=myDataCols[7][i]
            ii+=1
            for j in range(len(myColNames)):
                myNewDataCols[ j].append('')
                if j<17 :                     myNewDataCols[ j][ii]=myDataCols[j][i]
                if j>=17 and j<21:            myNewDataCols[ j][ii]=''
                if j==21:                     myNewDataCols[ j][ii]=myDataCols[19][i]

        if myDataCols[17][i]==myColNames[17]: myNewDataCols[17][ii]=myDataCols[18][i]    
        if myDataCols[17][i]==myColNames[18]: myNewDataCols[18][ii]=myDataCols[18][i]    
        if myDataCols[17][i]==myColNames[19]: myNewDataCols[19][ii]=myDataCols[18][i]    
        if myDataCols[17][i]==myColNames[20]: myNewDataCols[20][ii]=myDataCols[18][i]    

    print('4. Writing excel file...')
    mdexcel.list2ExcelFile(myNewDataCols,outFilePath, transpose=True)       

    print('5. The end ...')
    return True


def getOutputFileName(inputFileName:str)->str:
    if inputFileName.endswith('.xlsx'):
        return inputFileName.replace('.xlsx','.arreglat.xlsx')
    else: 
        return inputFileName.replace('.xls','.arreglat.xls')

def capturaExcel(inputFileNameEntry):
    fitxerExcel=tkinter.filedialog.askopenfilename( filetypes=[("Excel files", ".xlsx .xls")])
    inputFileNameEntry.delete(0,tkinter.END)
    inputFileNameEntry.insert(0,fitxerExcel)

def arreglarExcel(inputFileNameEntry):
    # Get the user input from the form
    inputFileName = inputFileNameEntry.get()
    outputFileName=getOutputFileName(inputFileName)
    arreglarXLS(inputFileName, outputFileName)
    tkinter.messagebox.showinfo("Success", "Proces acabat!")

def formArreglarXLS():
    # Create the main tkinter window
    root = tkinter.Tk()
    root.title("Arreglar fitxer Excel per a importar a ODOO")
    root.geometry('800x300')


    # Create labels and entry fields for each input
    #inputFileNameLabel= tkinter.Label(root, text="A capturar....:").grid(row=0, sticky=tkinter.W)
    inputFileNameLabel= tkinter.Label(root, text="Fitxer:")
    inputFileNameLabel.grid(row=0, column=0)
    #inputFileNameLabel.pack()
    inputFileNameEntry= tkinter.Entry(root, text='', width=70)
    inputFileNameEntry.grid(row=0, column=1)
    #inputFileNameEntry.pack()
    #botoCaptura = tkinter.Button(root, text ="Capturar Fitxer excel del padró", command = capturaExcel(inputFileNameEntry))
    botoCaptura = tkinter.Button(root, text ="Capturar", command = lambda:capturaExcel(inputFileNameEntry))
    botoCaptura.grid(row=0, column=2)
    #botoCaptura.pack()
    
    #executeButton = tkinter.Button(root, text="Arreglar Excel del Padró", command=arreglarExcel(inputFileNameEntry))
    executeButton = tkinter.Button(root, text="Arreglar Excel del Padró", command=lambda:arreglarExcel(inputFileNameEntry))
    executeButton.grid(row=5, column=1)
    #executeButton.pack()
    executeButton.grid()

    root.mainloop()

''''
------------------------------------------------------------
Tests
------------------------------------------------------------
'''
def mytest():
    #if __name__ == '__main__':
    #props=mdconfig.read_app_props("sqlite")
    
    '''
    inDir='/home/eduard/MyOdoo/Control-Presencia/DADES/'
    inFName='Padro_habitants_migracio_sedipualba240416.xlsx'
    outFName=inFName.replace('.xls','.out.xls')

    arreglarXLS(inDir+inFName, inDir+outFName)
    '''

    formArreglarXLS()
    
#------ main
mytest()