jueves, 25 de abril de 2024

ODOO (XVI) Consultar la base de datos Postgres

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


Para ello entramos en Odoo y seleccionameos en Configuraciopn 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 conectmos 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 realizxar ejecutables Windows y Linux para hacer ejecutables Linux.

Lo primero que hay que hacer es instalar pyinstaller

# En windows
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 Linux
pyinstaller --onefile mdph.py

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


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()    






jueves, 4 de abril de 2024

Escaneo de documentos en urbanismo.



  1. Para cada expediente se creará una carpeta dentro del NAS
  2. Se escanearan los documentos de ese expediente dentro de la carpeta creada. 
  3. Hay impresoras que permiten pasar el OCR. 
  4. Es importante que los documentos se pueda saber el orden que se ha escaneado pues marcará el orden del documento dentro del expediente.
  5. Si no se puede pasar el OCR, se podrá pasar posteriormente el OCR a través de adobe profesional u otros medios (easyOCR ..)
  6. Cuando se haya pasado el OCR se puede renombrar el documento en base al expediente y el tipo de documento que se obtenga analizando el texto del documento.
  7. Con estos datos se puede pasar a odoo.
  8. Mas tarde se rellenaran en odoo los datos del expediente y los departamentos que pertenezca el expediente.

martes, 20 de febrero de 2024

Python (II) Web Services Client (II) . Sedipualba & Castilla. Tablas importantes de Castilla

 Vamos a ver como mejoreamos el programa anterior para que también pueda ejecutar WS de Sedipualba.

1. Fichero de configuración YAML

Separamos la información de Castilla y la de Sedipualba. Los campos marcados con varias "x" son campos que cada uno debe de rellenar en base a su personalización. Este fichero lo he llamado x02_Config.yml


#----------------------------------------------------------------------------------------
# 01. SEDIPUALBA
#----------------------------------------------------------------------------------------
sedipualba:
demo:
username: xxxx
key: xxxxxxxxxx
api:
sefycu: https://pre-46xxx.sedipualba.es/sefycu/wssefycu.asmx?wsdl
segra: https://pre-46xxx.sedipualba.es/sefycu/wssegra.asmx?wsdl
segex: https://pre-46xxx.sedipualba.es/segex/wssegex.asmx?wsdl
seres_registro: https://pre-46xxx.sedipualba.es/seres/Servicios/wsseresregistro.asmx?wsdl
seres_ciudadano: https://pre-46xxx.sedipualba.es/seres/Servicios/wsseresregistro.asmx?wsdl
directorio: https://pre-46xxx.sedipualba.es/wsdirectorio.asmx?wsdl
sello: https://pre-46xxx.sedipualba.es/firma/wsselloelectronico.asmx?wsdl
notificaciones: https://pre-46xxx.sedipualba.es/sefycu/wsnotificaciones.asmx
entidad: 46xxx
dir3: L0146xxx
destino_registro: 16517
destino_factures: 18847
prod:
username: xxxx
key: xxxxxxxxxx
api:
sefycu: https://46xxx.sedipualba.es/sefycu/wssefycu.asmx?wsdl
segra: https://46xxx.sedipualba.es/sefycu/wssegra.asmx?wsdl
segex: https://46xxx.sedipualba.es/segex/wssegex.asmx?wsdl
seres_registro: https://46xxx.sedipualba.es/seres/Servicios/wsseresregistro.asmx?wsdl
seres_ciudadano: https://46xxx.sedipualba.es/seres/Servicios/wsseresregistro.asmx?wsdl
directorio: https://46xxx.sedipualba.es/wsdirectorio.asmx?wsdl
sello: https://46xxx.sedipualba.es/firma/wsselloelectronico.asmx?wsdl
notificaciones: https://46xxx.sedipualba.es/sefycu/wsnotificaciones.asmx
entidad: 46xxx
dir3: L0146xxx
destino_registro: 26265
destino_factures: 24180

xml: >
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<$FUNCTION$ xmlns="$XMLNS$">
<wsSegUser>$USERNAME$</wsSegUser>
<wsSegPass>$KEY$</wsSegPass>
<idEntidad>$ENTIDAD$</idEntidad>
$PARAMETERS$
</$FUNCTION$>
</soap:Body>
</soap:Envelope>
headers :
#Content-Type: application/soap+xml; charset=utf-8
Content-Type: text/xml; charset=utf-8

xmlns :
sefycu: https://eadmin.dipualba.es/sefycu/wssefycu.asmx
segra: https://eadmin.dipualba.es/sefycu/wssegra.asmx
segex: https://eadmin.dipualba.es/segex/wssegex.asmx
seres_registro: http://sedipualba.es/wsSeresV1.2
seres_ciudadano: http://sedipualba.es/wsSeresV1
directorio: https://sedipualba.es/wsdirectorio.asmx
sello: http://www.sedipualba.es/firma/WSSelloElectronico.asmx
notificaciones: /admin/
#----------------------------------------------------------------------------------------
# 02. CASTILLA
#----------------------------------------------------------------------------------------
castilla:
api_url: https://rrhh-xxxxxxxx.grupocastilla.es/epsilonnetws/WSEmpleado.asmx?wsdl
headers :
Content-Type: application/soap+xml; charset=utf-8
token: >
&lt;TOKEN&gt;
&lt;AppName&gt;$APPNAME$&lt;/AppName&gt;
&lt;AppToken&gt;xxxxxxx&lt;/AppToken&gt;
&lt;/TOKEN&gt;
appName: xxxx_APPS
nombreTabla: PERSONAS
xml: >
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body>
<ConsultaGenericaBD_XML xmlns="http://www.grupocastilla.es/epsilonnetws">
<sToken>$TOKEN$</sToken>
<nombreTabla>$TABLA$</nombreTabla>
<condicion>$CONDICION$</condicion>
</ConsultaGenericaBD_XML>
</soap:Body>
</soap:Envelope>


2. Clases de utilidad para trabajar con ficheros yXML

La primera clase es simple y es para escribir datos a un fichero ya sea en ruta absoluta o relativa

La segunda es para hacer unas pocas cosas con XML

Veamos el primer fichero u02_File.py 

import sys

'''
File Utils
'''

class FileUtils:
#------------------------------------------------------
# 01. Create the XML for making the WS call
# params:
# 1. condicion for instance "APELLIDO1 LIKE '%PEREZ%'"
#------------------------------------------------------
@staticmethod
def writeDataToFileAbsolute(absoluteFileNamePath, data):
fileOut=open(absoluteFileNamePath,"w") # Rewrite
fileOut.write(data)
fileOut.close

@staticmethod
def writeDataToFileRelativeFile(relativeFileNamePath, data):
#fileName = sys.path[0] + "/../my-python-programs-output/p01-request-01.output.xml"
fileName = sys.path[0] + "/" + relativeFileNamePath
print ('fileName:',fileName)
fileOut=open(fileName,"w") # Rewrite
fileOut.write(data)
fileOut.close


Veamos el segundo fichero u02_XML.py 

import xml.etree.ElementTree as ET

'''
XML Utils
'''
class XMLUtils:
#------------------------------------------------------
# Get a XML by tab
# params:
# 1. firstElem: xml string to read
#------------------------------------------------------
@staticmethod
def getElementByTag(xmlString, tagString):
elem=ET.fromstring(xmlString)
tabla=elem.tag
level=0
while tabla!=tagString and level<10:
elem=elem[0]
tabla=elem.tag
level+=1
return elem

#------------------------------------------------------
# Get an array of records as an array of dictionaries
# params:
# 1. firstElem: xml string to read
#------------------------------------------------------
@staticmethod
def getRecordsCastilla(firstElem):
records=[]
for secondElem in firstElem: # REGISTRO
#print('100 ',secondElem.tag, secondElem.attrib, secondElem.text)
dictio={}
for thirdElem in secondElem: # CAMPO
#print('200 ',thirdElem.tag, '--', thirdElem.attrib,'----', thirdElem.text)
att = thirdElem.attrib['NOMBRE']
value = thirdElem.text
dictio[att]=value
#print ('dictio=',dictio)
records.append(dictio)
return records


Tablas importantes

PUES_TRAB

    - id_trabajador: ('00075'???)

    -id_nivel ('00068')

    -id_secuencia ('004')


NIV_ORB

    -id_nivel ('00068')

    -d_nivel ("Tecnic nivell xx informatica")

    -id_puesto ('00013')


TRABAJADORES

    -id_empresa

    -id_trabajador

    -id_secuencia

Y ya buscamos en PERSONAS para obtener el resto de dtos