OpenERP
OpenERP és un producte de codi obert que es distribueix sota dos tipus de desplegament: On-premise, sota els SO Linux i Windows, amb dues versions (Community, gratuïta, i Enterprise, de pagament); i el SaaS.
La llicència de la versió 6 i 7 és és AGPLv3 o AGPL+Private User.
L’OpenERP és desenvolupat sota una arquitectura web client-servidor de tres capes:
- La base de dades en un servidor PostgreSQL
- Un servidor OpenERP que conté tota la lògica de negoci
- Un servidor web, que en la versió 6.x està ubicat conjuntament amb el servidor OpenERP
- La capa client que té diverses possibilitats: web, GTK i possibles clients desenvolupats amb els protocols XML-RPC i Net-RPC
Instal·lació
Podem seguir aquest manual https://doc.openerp.com/7.0/es/install/#installation-link
Explotació i adequació
L’explotació i adequació de l’ERP d’una organització és una tasca imprescindible, ja que garanteix que el programari es mantingui en condicions de ser utilitzat per l’organització per tal de donar sortida a les seves necessitats. Per poder-ho dur a terme, per una banda cal identificar les necessitats (tasca pròpia de consultors) i, per una altra, tenir un coneixement profund de l’ERP, tant en les funcionalitats que facilita (tasca de consultors i implantadors) com en les qüestions tècniques vinculades a l’ERP (tasca d’analistes i programadors).
L’OpenERP és un programari de gestió empresarial desenvolupat sobre el framework OpenObject de tipus RAD (Rapid Application Development).
La facilitat dels entorns RAD rau en el fet que el desenvolupament d’aplicacions és molt simple pel programador, de manera que amb poc esforç es pot obtenir aplicacions d’altes prestacions.
L’OpenObject facilita diversos components que permeten construir l’aplicació:
- La capa ORM (Object Relational Mapping) entre els objectes Python i la base de dades PostgreSQL. El dissenyador-programador no efectua el disseny de la base de dades; únicament dissenya classes, per les quals la capa ORM d’OpenObject n’efectuarà el mapat sobre el SGBD PostgreSQL.
- Una arquitectura MVC (model-vista-controlador) en la qual el model resideix en les dades de les classes dissenyades amb Python, la vista resideix en els formularis, llistes, calendaris, gràfics… definits en fitxers XML i el controlador resideix en els mètodes definits en les classes que proporcionen la lògica de negoci.
- Un sistema de fluxos de treball o workflows.
- Dissenyadors d’informes.
- Facilitats de traducció de l’aplicació a diversos idiomes.
Tal com es pot observar, són molts els components d’OpenObject a conèixer per poder adequar l’OpenERP a les necessitats de l’organització, en cas que les funcionalitats que aporta l’OpenERP, tot i ser moltes, no siguin suficients.
La base de dades d'OpenERP
En l’OpenERP no hi ha un disseny explícit de la base de dades, sinó que la base de dades d’una empresa d’OpenERP és el resultat del mapatge del disseny de classes de l’ERP cap el SGBD PostgreSQL que és el que proporciona la persistència necessària per als objectes.
En conseqüència, l’OpenERP no facilita cap disseny entitat-relació sobre la base de dades d’una empresa ni tampoc cap diagrama del model relacional.
Si sorgeix la necessitat de detectar la taula o les taules on resideix determinada informació, és perquè es coneix l’existència d’aquesta informació gestionada des de l’ERP i, per tant, es coneix algun formulari de l’ERP a través del qual s’introdueix la informació.
L’OpenERP possibilita, mitjançant els clients web i GTK, recuperar el nom de la classe Python que defineix la informació que s’introdueix a través d’un formulari i el nom de la dada membre de la classe corresponent a cada camp del formulari. Aquesta informació permet arribar a la taula i columna afectades, tenint en compte dues qüestions:
- El nom de les classes Python d’OpenERP sempre són en minúscula (s’utilitza el guió baix per fer llegible els mots compostos) i segueix la nomenclatura nom_del_modul.nom1.nom2.nom3… en la qual s’utilitza el punt per indicar un cert nivell de jerarquia. Cada classe Python d’OpenERP és mapada en una taula de PostgreSQL amb moltes possibilitats que el seu nom coincideixi amb el nom de la classe, tot substituint els punts per guions baixos.
- Els noms dels atributs d’una classe Python sempre són en minúscula (s’utilitza el guió baix per fer llegible els mots compostos). Cada dada membre d’una classe Python d’OpenERP que sigui persistent (una classe pot tenir dades membres calculades no persistents) és mapat com un atribut en la corresponent taula de PostgreSQL amb el mateix nom.
D’aquesta manera, coneixent el nom de la classe i el nom de la dada membre, és molt possible conèixer el nom de la taula i de la columna corresponents. Es pot configurar els clients web i GTK per tal que informin del nom de la classe i de la dada membre en situar el ratolí damunt les etiquetes dels camps dels formularis.
Figura 1.1 Activar el debug mode
Si observem la figura 1.2, podem observar com:
Dalt apareix un desplegable que diu depurar#574, és la vista per defecte per a analitzar els elements dels formularis.
El camp Referencia cliente es diu client_order_ref de l'objecte sale.order. Per tant, la columna client_order_ref de la taula sale_order.
Figura 1.2 Dades de depuració
Respecte al desplegable de dalt, la resta d'opcions són:
- View Fields que permet obtenir una llista dels camps de la vista actual, amb els paràmetres corresponents.
- Fields View Get que mostra l’XML generat per la vista actual. Cal tenir en compte que si la vista s’ha generat a partir de diverses vistes XML heretant les unes de les altres, aquí se’n mostra el resultat final.
- Manage Views que mostra un llistat de les vistes relacionades amb la vista actual. Des d’aquest punt es poden crear noves vistes, eliminar-les o editar-les, encara que és recomanable utilitzar aquesta funcionalitat només per consultar. Es recomana realitzar les modificacions creant nous mòduls, per no perdre les modificacions davant actualitzacions de l’ERP.
- Edit TreeView, Edit SearchView, Edit Action i Edit Workflow que serveixen per accedir a l’edició de les vistes relacionades amb la vista actual.
- Si estem editant un registre (mode formulari) o consultant-lo (mode pàgina), apareix una nova opció View Log (perm_read) que mostra informació relativa al registre actual.
Accés de només lectura a les dades
Les empreses acostumen a tenir, entre els seus responsables, usuaris finals que poden efectuar consultes no previstes a la base de dades i que, per aconseguir-ho, poden utilitzar eines gràfiques per elaborar consultes o fins i tot, si són prou espavilats, executar consultes SQL des d’una consola d’accés.
En aquesta situació, cal facilitar als usuaris que correspongui, un usuari per accedir al SGBD PostgreSQL amb els privilegis d’accés, en mode consulta, als objectes de la base de dades que correspongui. S’aconsella seguir els següents passos:
1. Crear els usuaris de SGBD amb les contrasenyes que corresponguin.
Amb pgAdmin és fàcil d'afegir un nou rol de login.
2. Donar els privilegis d’accés adequats als usuaris que corresponguin.
Desenvolupament de mòduls en OpenERP
Anem a desenvolupar un mòdul molt simple d'exemple mentre expliquem cóm se fan els mòduls. Aquest serà per a una academia que tindrà professors, cursos i alumnes.
Estructura bàsica dels mòduls
Un mòdul pot contenir els següents elements:
- Objecte de negoci: declara com classes de Python qual hereta de la classe osv.Model, la persistència d'aquests recursos és manejada completament per l'ORM d'OpenERP.
- Dades: Arxius XML/CSV amb les metadades (vistes i la declaració de fluxos de treball), les dades de configuració (parametrització de mòduls) i les dades de demostració.
- Informes: RML (format XML). HTML / MAKO o plantilles d'informes d'OpenOffice, que es combinaran amb qualsevol tipus de dades de negocis i generar HTML, ODT o informes en PDF.
Cada mòdul està contingut en el seu propi directori al servidor al directori de addons, configurat a la instal·lació del servidor. Per crear un nou mòdul, són necessaris els següents passos:
- Crear un subdirectori en el directori de addons
- Crear el __ init__.py per a l'importació d'arxius.
- Crear el __ openerp__.py que és el que defineix el mòdul.
- Crear arxius de Python que continguen objectes
- Crear. xml que contenen dades de mòduls com ara vistes, entrades de menú o dades de demostració
- Opcionalment crear informes i fluxos de treball
__init__.py
És l'arxiu d'importació de Python, perquè un mòdul d'OpenERP és també un mòdul ordinari de Python. L'arxiu ha d'importar tots els altres arxius python o submòduls.
Per exemple, si un mòdul conté un sol arxiu de python anomenat openacademy.py, l'arxiu ha de ser similar a:
import openacademy
__openerp__.py
Aquest arxiu, que ha de ser un diccionari Python literal, és responsable de:
- Determinar els arxius XML que es va a analitzar durant la inicialització del servidor
- Determinar les dependències del mòdul creat.
- Declarar metadades addicionals
Aquest fitxer ha de contenir un diccionari de Python amb els següents valors:
- name: el nom del mòdul, en anglès.
- version: la versió del mòdul.
- description: la descripció del mòdul (text).
- author: l’autor del mòdul.
- website: el lloc web de l’autor del mòdul.
- license: la llicència del mòdul (per defecte, la que correspon a la versió de servidor OpenERP on s’instal·larà el mòdul).
- category: categoria a la qual pertany el mòdul. Text separat per barres / amb la ruta jeràrquica de les categories.
- depends: llista Python de mòduls dels quals depèn aquest mòdul. El mòdul base s’acostuma a afegir perquè conté dades utilitzades en vistes, informes…
- init_xml: llista Python de noms de fitxers XML que es carregaran en iniciar el servidor OpenERP amb l’opció -init=module. Les rutes dels fitxers han de ser relatives a la carpeta on és el mòdul. En aquesta llista s’acostuma a posar els fitxers relatius a definició de workflows i les dades a carregar en el procés d’instal·lació del mòdul.
- update_xml: llista Python de noms de fitxers XML que es carregaran en iniciar el servidor OpenERP amb l’opció -update=module. Les rutes dels fitxers han de ser relatives al directori on és el mòdul. En aquesta llista s’inclouen els fitxers relatius a vistes, informes i assistents.
- demo_xml: llista Python de noms de fitxers XML que es carregaran si s’ha instal·lat el servidor OpenERP amb dades de demostració o exemple. Les rutes dels fitxers han de ser relatives al directori on és el mòdul.
- installable: els valors permesos són True o False i determinen si el mòdul és o no és instal·lable.
- active: els valors permesos són True o False i determinen si el mòdul serà instal·lat automàticament en el procés de creació de l’empresa. Per defecte, False.
En el cas de del nostre exemple de openacademy tindrà un aspecte similar a:
{ 'name' : "OpenAcademy", 'version' : "1.0", 'author' : "OpenERP SA", 'category' : "Tools", 'depends' : ['mail'], 'data' : [ 'openacademy_view.xml', 'openacademy_data.xml', 'report/module_report.xml', 'wizard/module_wizard.xml', ], 'demo' : [ 'openacademy_demo.xml' ], 'installable': True, }
Objectes
Tots els recursos OpenERP són objectes: factures, socis. Les metadades també són objecte també: menús, accions, informes ... Els noms d'objectes són jeràrquics, com en els exemples següents:
account.transfer: una transferència de diners account.invoice: una factura account.invoice.line: una línia de factura
En general, la primera paraula és el nom del mòdul.
Els objectes es declaren en python com a subclasse de osv.Model
El ORM d'OpenERP es construeix sobre PostgreSQL. Per tant, és possible consultar l'objecte utilitzat per OpenERP utilitzant la interfície d'objecte (ORM) o mitjançant l'ús d'instruccions SQL directament.
Però és perillós per escriure o llegir directament a la base de dades PostgreSQL, com es pot escurçar passos importants com la comprovació de restriccions o la modificació de flux de treball.
Arxius XML
Arxius XML ubicats al directori dels mòduls s'usen per inicialitzar o actualitzar la base de dades quan s'instal·la o actualitza el mòdul. S'utilitzen per a molts fins, entre els quals podem citar:
- Inicialització i declaració de dades de demostració,
- Declaració de vistes,
- Declaració dels informes,
- Declaració dels fluxos de treball
Les dades poden ser inserides o actualitzades en les taules de PostgreSQL corresponents als objectes OpenERP utilitzant fitxers XML. L'estructura general d'un fitxer XML OpenERP és el següent:
<?xml version="1.0"?> <openerp> <data> <record model="model.name_1" id="id_name_1"> <field name="field1"> "field1 content" </field> <field name="field2"> "field2 content" </field> (...) </record> <record model="model.name_2" id="id_name_2"> (...) </record> (...) </data> </openerp>
Exemple de Creació d'un mòdul bàsic
Diseny de mòduls amb DIA
Disseny del model
L’eina de diagramació Dia, amb l’extensió per generar mòduls per a OpenERP, facilita el disseny de mòduls per OpenERP. Les possibilitats que permet l’eina Dia són, però, limitades i, en conseqüència, ens cal aprendre com escriure directament els mòduls utilitzant els llenguatges Python i XML.
Un mòdul d’OpenERP incorpora, en el seu interior, tots els elements del patró model-vista-controlador en el qual es basa el desenvolupament d’OpenObject. L’eina Dia permet definir el model via diagrama UML i l’extensió per generar mòduls per a OpenERP ens facilita el mòdul que conté:
- El model, definit a través de llenguatge Python, corresponent al diagrama UML dissenyat.
- Una vista, definida a través del llenguatge XML, generada de forma automàtica.
- Un controlador buit, ja que l’eina Dia no permet dissenyar els mètodes.
Definició d'objectes OpenERP amb Python
El model en OpenERP es descriu a través de classes escrites en Python i OpenObject s’encarrega de fer-les persistents en el SGBD PostgreSQL.
Per definir un objecte (classe) d’OpenERP cal definir una classe de Python i posteriorment instanciar-la, fet que provoca la creació de l’objecte (classe) d’OpenERP. La classe ha d’heretar obligatòriament de la classe osv del mòdul osv.
L’esquelet de la classe Python que permet definir l’objecte OpenERP té la forma:
class name_of_the_object (osv.osv): _name = 'name.of.the.object' _columns = {...} ... name_of_the_object()
Les classes Python que permeten definir objectes (classes) d’OpenERP tenen dos atributs obligatoris i altres opcionals. Els atributs obligatoris són:
- _name: nom de l’objecte (classe) d’OpenERP. Aquest nom s’utilitza de forma automàtica per generar el nom de la taula de PostgreSQL, i canvia els punts per guions baixos.
- _columns: diccionari Python amb la declaració de les dades de l’objecte (classe) d’OpenERP, que passaran a ser les columnes de la taula de PostgreSQL.
Els atributs opcionals són:
- _auto: els valors possibles són True i False. Per defecte True. Determina si s’ha de generar la taula PostgreSQL a partir de la definició de l’objecte OpenERP. El valor False s’utilitza per objectes OpenERP no persistents que es basen en vistes definides en PostgreSQL.
- _constraints: definició de restriccions sobre l’objecte, definides amb codi Python.
- _sql_constraints: definició de restriccions SQL sobre l’objecte, definides a la corresponent taula de PostgreSQL.
- _defaults: diccionari Python que conté els valors per defecte per les dades (definides a _columns) de l’objecte (classe) d’OpenERP que ho precisin.
- _inherit: objecte (classe) d’OpenERP del qual hereta l’objecte (classe) que estem definint.
- _inherits: llista d’objectes (classes) d’OpenERP dels quals hereta l’objecte (classe) que estem definint. La llista ha de seguir la sintaxi d’un diccionari Python de la forma:
{'nom_objecte_pare':'nom_de_camp',...}
- _log_access: valors possibles True i False. Per defecte, True. Indica si els accessos d’escriptura als recursos (objectes) han de quedar enregistrats. En cas afirmatiu, de forma automàtica es creen a la corresponent taula de PostgreSQL les columnes create_uid, create_date, write_uid i write_date per enregistrar els accessos d’escriptura.
- _order: nom dels atributs utilitzats per ordenar els resultats dels mètodes de cerca i lectura. Per defecte, s’utilitza el camp _id generat automàticament en totes les taules de PostgreSQL corresponents a objectes (classes) d’OpenERP. Exemples:
_order = 'name'
_order = 'date_order desc'
- _rec_name: nom de l’atribut de l’objecte (classe) d’OpenERP que conté el nom informatiu de cada recurs (objecte) concret. Per defecte name.
- _sequence: nom de la seqüència SQL que gestiona els identificadors (contingut del camp _id) pels recursos d’aquest objecte (classe) d’OpenERP. Per defecte, nomDeLaTaula_id_seq.
- _sql: codi SQL a executar en la creació de l’objecte, en cas que _auto sigui True.
- _table: nom de la taula SQL si es vol que sigui diferent del nom automàtic a partir del nom de l’objecte substituint els punts per guions baixos.
Una vegada presentats els possibles atributs de les classes Python dissenyades per generar objectes (classes) d’OpenERP, ens cal endinsar-nos en els possibles continguts d’alguns d’aquests atributs.
Tipus de Camps
L’atribut _columns de la classe Python que defineix l’objecte (classe) d’OpenERP ha de contenir un diccionari Python amb la definició dels atributs de l’objecte d’OpenERP, a partir dels camps (fields) predefinits a la classe osv.
La sintaxi general per definir una columna és la següent:
'nom_camp':fields.tipus_de_camp(paràmetres)
on tipus_de_camp ha de ser un dels tipus facilitats per la classe osv i el contingut de paràmetres depèn del tipus del camp.
Hi ha diverses categories de camps:
- Camps simples: boolean, integer, float, char, text, date, datetime, binary i selection.
- Camps relacionals: many2one, one2many, many2many i related.
- Camps function.
- Camps property.
Els tipus de camps simples i relacionals habituals (many2one, one2many i many2many) ja ens són familiars a causa de la seva utilització en el disseny de mòduls amb l’eina de diagramació Dia. Per a tots aquests camps, el contingut de paràmetres en la crida fields.tipus_de_camp(paràmetres) es correspon amb el contingut de l’espai valor en la definició de cada atribut de classe mitjançant l’eina Dia.
Tipus relacional related
Els camps related permeten accedir a un camp d’un altre objecte referenciat des del propi objecte; és a dir, com si es tractés de camps encadenats.
Com a exemple, suposem que en un mòdul d’OpenERP tenim les classes exemplificades en el diagrama UML de la figura. En un mòdul per a OpenERP, a la classe City hi haurà un camp relacional many2one cap a la classe State, i a la classe State hi haurà un camp relacional many2one cap a la classe Country. Un camp related ens permet accedir des de la classe City a la classe Country via la classe State.
Figura Disseny UML amb dues relacions many2one
La definició d’una columna related ha de seguir la sintaxi:
'nom': fields.related('campPontPropi','campOnAccedir', type='tipusDelCamp', string='etiquetaNouCamp' [,store=True/False][,...]),
Els punts suspensius del final indiquen que hi pot haver més paràmetres segons el tipus del camp. Així, en els camps de tipus many2one, caldrà afegir un paràmetre relation per indicar el nom de la classe relacionada. El paràmetre store (per defecte False) permet indicar si el valor accedit ha de quedar enregistrat a la base de dades (cas clar de redundància) per tal de facilitar una major eficiència d’OpenERP en els processos de recerca i de lectura. En cas d’activar aquesta redundància, els valors sempre són mantinguts per OpenERP i mai pels usuaris.
Així, per aconseguir que la classe City de la figura pugui accedir a l’identificador de la classe Country via la classe State, hauríem de tenir:
# Dins la classe module.state: _columns: { ... 'country_id': fields.many2one('module.country','Country'), ... }
# Dins la classe module.city: _columns: { ... 'state_id': fields.many2one('module.state','State'), 'country_id': fields.related('state_id,'country_id', type='many2one', relation='module.country', string='Country',store=False), ... }
Un cas real d’utilització el podem observar en el disseny de l’objecte (classe) res.partner d’OpenERP, on veiem les columnes city, function, subname, phone, mobile, country i email com a camps related a través del camp address de la mateixa classe:
_columns = { ... 'address': fields.one2many('res.partner.address', 'partner_id','Contacts'), ... 'city': fields.related('address', 'city', type='char', string='City'), 'function': fields.related('address', 'function', type='char', string='function'), 'subname': fields.related('address', 'name', type='char', string='Contact Name'), 'phone': fields.related('address', 'phone', type='char', string='Phone'), 'mobile': fields.related('address', 'mobile', type='char', string='Mobile'), 'country': fields.related('address', 'country_id', type='many2one', relation='res.country',string='Country'), 'email': fields.related('address', 'email', type='char', size=240, string='E-mail'), ... }
Exemple d'utilització dels camps related:
L’objecte (classe) school.professor del mòdul school que estem millorant incorpora l’atribut address_id que permet accedir a l’adreça del professor (objecte res.partner.address).
En executar el formulari de manteniment de professors, apareix el camp Private Address amb el contingut identificador de l’adreça. Si es vol tenir accés a tota la informació continguda a l’adreça (carrer, telèfon, correu electrònic…) el formulari permet accedir a la fitxa de l’adreça.
Imaginem-nos que és important, per a nosaltres, que el telèfon de cada professor es vegi directament a la fitxa del professor, sense haver d’accedir a la fitxa de l’adreça.
Per aconseguir-ho, com que el telèfon del professor resideix a res.partner.address i ja accedim a aquest objecte a través del camp address_id, podem definir el següent camp _related:
'phone':fields.related('address_id','phone',type='char',size=64, string='Phone',store=False,readonly=True),
Tipus function
Els camps function simulen camps reals però es calculen mitjançant una funció Python enlloc d’emmagatzemar-se a la base de dades PostgreSQL. En casos especials, per augmentar la velocitat de consulta d’OpenERP i facilitar les consultes, es fan redundants a la base de dades, és a dir, s’emmagatzemen a la base de dades, però sempre són calculats i actualitzats a través de funcions i mai pels usuaris.
La definició d’una columna function ha de seguir la sintaxi:
'nom': fields.function(fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='Float', fnct_search=None, string='etiquetaNouCamp', store=False, multi=False [,...]),
en la qual:
- Els paràmetres que van acompanyats d’un símbol =Valor no són obligatoris i el valor indicat és el que es considera en cas de no explicitar el paràmetre.
- type: és el tipus de camp retornat per la funció. Pot ser qualsevol tipus excepte function. Els punts suspensius del final indiquen que hi pot haver més paràmetres segons el tipus del camp. Així, en els camps de tipus 'many2one' caldrà afegir un paràmetre obj per indicar el nom de la classe relacionada.
- store: per indicar si el camp ha de residir a la taula de la base de dades (redundància).
- multi: per indicar que el càlcul de la funció s’efectuï per a diversos camps, a causa que la mateixa funció és invocada en diferents camps (veure, com exemple, el mòdul auction d’OpenERP).
- fnct: és el mètode que calcula el valor del camp. És un paràmetre obligatori i ha d’existir abans de declarar el camp funcional. Ha de retornar, obligatòriament, un diccionari de parelles de la forma {id1:valor1, id2:valor2,…} en el qual id1, id2… han de ser identificadors d’objectes i valor1, valor2… els corresponents valors que han de correspondre amb el tipus indicat a type. La sintaxi ha de ser:
def fnct(self, cr, uid, ids, field_name, arg, context)
- fnct_inv: és un mètode utilitzat per escriure un valor en lloc del camp. La sintaxi ha de ser:
def fnct_inv(obj, cr, uid, id, name, value, fnct_inv_arg, context)
- fnct_search: és el mètode utilitzat per fer recerques per aquest camp. Ha de retornar la llista de tuples que especifiquin el criteri de cerca a utilitzar pel mètode search facilitat per OpenObject. La sintaxi ha de ser:
def fnct_search(obj, cr, uid, obj, name, args)
Els mòduls d’OpenERP estan plens d’exemples de camps funcionals, però no tots són senzills d’entendre en una primera aproximació al tema. Així, per iniciar-nos en els camps funcionals, podem centrar-nos en l’atribut number_of_days del mòdul hr_holidays. Seria interessant que, si no el teniu instal·lat, procedíssiu a instal·lar aquest mòdul, per poder comprovar el que comentarem a continuació.
Fixeu-vos en la part de la definició de la classe que ens interessa comentar:
class hr_holidays(osv.osv): ... def _compute_number_of_days(self, cr, uid, ids, name, args, context=None): result = {} for h in self.browse(cr, uid, ids, context=context): if h.type=='remove': result[h.id] = -h.number_of_days_temp else: result[h.id] = h.number_of_days_temp return result _columns = { ... 'type': fields.selection([('remove','Leave Request'), ('add','Allocation Request')], 'Request Type', required=True,readonly=True, ...), 'number_of_days_temp': fields.float('Number of Days', readonly=True, states={'draft':[('readonly',False)]}), 'number_of_days': fields.function(_compute_number_of_days, string='Number of Days',store=True), ... } ...
El mètode _compute_number_of_days crida el mètode browse facilitat per OpenObject. La llista dels mètodes facilitats per OpenObject es troba a l’apartat “OpenERP: la vista i el controlador”.
Fixem-nos que number_of_days és un atribut funcional que, malgrat ser calculat mitjançant una funció, el seu resultat resta emmagatzemat a la base de dades, a causa de store=True. Podem comprovar, mirant la descripció de la taula hr_holidays a la taula de PostgreSQL, l’existència de la columna number_of_days.
Si utilitzeu el mòdul, observareu que permet gestionar, per cada empleat, els dies d’absència i els dies treballats fora del calendari laboral. Cada recurs (objecte) de l’objecte (classe) hr.holidays d’OpenERP correspon a un dels dos tipus possibles (add o remove) segons la definició de l’atribut type. L’atribut number_of_days_temp conté el nombre de dies afectats, sempre en positiu. En canvi, l’atribut number_of_days vol contenir el nombre de dies afectats, en positiu si es tracta de dies de treball addicional, i en negatiu si es tracta de dies d’absència.
En conseqüència, el camp number_of_days es pot calcular a partir del camp number_of_days_temp i per això es defineix com un camp funcional, que executa el mètode _compute_number_of_days definit prèviament.
Observem també que el mètode invocat retorna el diccionari anomenat result format per una única parella on la clau és l’identificador del recurs sobre el qual s’executa i el valor és el nombre de dies calculats.
Tipus property
Els camps property són camps dinàmics amb drets d’accés específics, que permeten contenir diferents valors en funció de la companyia.
Sembla clar que el lloc on hem de cercar aquests camps té a veure amb l’objecte (classe) res.partner d’OpenERP. Si fem una ullada a aquesta classe, definida dins el mòdul base d’OpenERP (fitxer res_partner.py) no hi trobarem cap dels dos camps property indicats. Els camps que cerquem es troben dins l’objecte res.partner definit dins el mòdul account (fitxer partner.py del mòdul account) que és una classe derivada de la classe res.partner principal (l’existent en el mòdul base). Vegem la part corresponent als dos camps property de la definició d’aquesta classe derivada:
class res_partner(osv.osv): _name = 'res.partner' _inherit = 'res.partner' _description = 'Partner' ... _columns = { ... 'property_account_payable': fields.property('account.account', type='many2one', relation='account.account', string="Account Payable", view_load=True, domain="[('type', '=', 'payable')]", help="This account will be used instead of the default one as the payable account for the current partner", required=True), 'property_account_receivable': fields.property('account.account', type='many2one', relation='account.account', string="Account Receivable", view_load=True, domain="[('type', '=', 'receivable')]", help="This account will be used instead of the default one as the receivable account for the current partner", required=True), ... }
Valors per defecte
La classe Python per definir els objectes d’OpenERP incorpora l’atribut opcional _defaults que permet la definició dels valors per defecte per un o diversos tipus de dades simples de l’objecte d’OpenERP.
Els valors per defecte dels camps d’un objecte d’OpenERP es defineixen en un diccionari de la forma:
_defaults = { 'nom_del_camp1': funció1 o valor1, 'nom_del_camp2': funció2 o valor2, ... }
Observem que els valors per defecte (sempre corresponents a tipus de dades simples) poden ser valors estàtics o valors dinàmics resultants de la crida de funcions que han d’estar declarades amb anterioritat a la definició del diccionari _defaults.
Les funcions per ser invocades en un valor per defecte dinàmic, han de contenir obligatòriament 4 paràmetres:
- obj: objecte osv corresponent al recurs que s’està creant.
- cr: cursor de base de dades.
- uid: ID de l’usuari que està donant d’alta el recurs.
- context: el context actual (facilitat pel client).
Els mòduls d’OpenERP estan plens d’exemples d’utilització de valors per defecte. Així, per exemple, observem els valors per defecte de l’objecte res.users (definit en el fitxer res_users.py dins el mòdul base) ideat per gestionar els usuaris de l’empresa gestionada per OpenERP:
_defaults = { 'password' : , 'context_lang': 'en_US', 'active' : True, 'menu_id': _get_menu, 'company_id': _get_company, 'company_ids': _get_companies, 'groups_id': _get_group, 'menu_tips': False }
En aquesta definició observem la coexistència de valors estàtics (password, llenguatge, active i menu_tips) i valors dinàmics (menu_id, company_id, company_ids i groups_id). Si observeu la definició completa de la classe res.users podreu comprovar com abans de la definició del diccionari _defaults es troba la definició de diverses funcions, entre les quals hi ha les quatre funcions invocades des de _defaults (_get_menu, _get_company, _get_companies i _get_group). Veureu, també, que les quatre funcions tenen els paràmetres self, cr, uid i context.
En algunes ocasions, la definició d’un valor per defecte estàtic s’efectua amb la crida a una funció lambda de Python sense paràmetres. La definició dels valors per defecte estàtics a l’anterior exemple s’hagués pogut efectuar com:
_defaults = { 'password': lambda *a: , 'context_lang': lambda *a: 'en_US', 'active': lambda *a: True, 'menu_id': _get_menu, 'company_id': _get_company, 'company_ids': _get_companies, 'groups_id': _get_group, 'menu_tips': lambda *a: False }
Per últim, per no veure’s obligats a crear funcions que només siguin invocades en un valor _defaults, es pot utilitzar les funcions lambda de Python en el mateix moment d’efectuar la crida, estalviant-nos la definició de la funció amb nom. En el següent codi corresponent al diccionari _defaults de la classe document.directory (fitxer document_directory.py del mòdul document) observem la definició de dues funcions lambda:
_defaults = { 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'document.directory', context=c), 'user_id': lambda self,cr,uid,ctx: uid, 'domain': '[]', 'type': 'directory', 'ressource_id': 0, 'storage_id': _get_def_storage, 'resource_find_all': True, }
Exemple d'utilització de l'atribut _defaults
L’objecte (classe) school.professor del mòdul school que estem millorant, incorpora l’atribut contract que consisteix en una selecció entre dos possibles valors. En el procés de creació d’un nou professor podem observar que el camp contract apareix sense valor per defecte. Volem definir que el valor per defecte d’aquest camp sigui trainee.
Així mateix, observem que l’objecte (classe) school.professor no incorpora el camp especial active que permet ocultar els recursos (objectes) de la classe quan ja no interessi, sense eliminar-los. Aprofitarem aquest exemple per afegir aquest camp i posar-li el valor 1 (cert) com a valor per defecte.
El codi de la classe school.professor modificada és:
class school_professor(osv.osv): """Professors""" _name = 'school.professor' _columns = { 'name': fields.char('Professor Name', size=64,required=True), 'contract': fields.selection([('trainee','Trainee'), ('named','Named')],'Contract'), 'partner_id': fields.many2one('res.partner', 'Associated Partner'), 'address_id': fields.many2one('res.partner.address', 'Private Address'), 'phone': fields.related('address_id','phone', type='char', string='Phone', store=False, readonly=True), 'hours_available': fields.integer('Hours Per Week'), 'course_ids': fields.one2many('school.course', 'prof_id', 'Courses'), 'active': fields.boolean('Active'), } _defaults = { 'contract': lambda *a: 'trainee', 'active': lambda *a: 1, } school_professor()
Observem que en la definició dels valors per defecte, hem utilitzat una funció lambda de Python, però no hagués estat necessari.
Una vegada actualitzat el mòdul school amb les noves millores, si obrim el formulari de manteniment de professors observarem que en donar d’alta un professor, en el camp Contract apareix per defecte el valor Trainee. En canvi, no apareix el nou camp Active, a causa que no hem modificat encara el formulari XML, però sí que podem veure la seva existència a la taula school_professor de la base de dades, amb valor True per tots els professors que ja existien a la taula i també pels nous professors.
Podeu trobar la versió millorada del mòdul school a l’arxiu school_03.zip dins l’apartat “Mòduls OpenERP” dels annexos del web. Restriccions
Els objectes (classes) d’OpenERP poden incorporar, de forma opcional, restriccions d’integritat, addicionals a les que es puguin establir sobre la pròpia base de dades. OpenERP valida aquestes restriccions en les modificacions de dades i, en cas de violació, OpenERP mostra una pantalla d’error.
Les restriccions d’un objecte d’OpenERP es declaren a la classe Python que defineix l’objecte amb un atribut _constraints consistent en una llista de tuples, on cada tuple correspon a una restricció:
_constraints = [ (nomMètode,'missatgeError','llistaNomsCamps') (nomMètode,'missatgeError','llistaNomsCamps'), ... ]
Els tuples corresponents a una restricció venen definits per tres camps:
- nomMètode: és el nom del mètode de l’objecte utilitzat per validar la restricció. El seu prototipus ha de tenir la forma def _nomMètode (self, cr, uid, ids) i ha de retornar un valor booleà.
- missatgeError: és el missatge d’error que es mostrarà a l’usuari si la comprovació falla.
- llistaNomsCamps: és la llista dels noms dels camps que s’afegeixen al missatge d’error amb l’objectiu d’ajudar a l’usuari a entendre el motiu pel qual ha fallat la validació de la restricció.
Els mòduls d’OpenERP incorporen validacions _constraints en nombrosos llocs. Un cas el trobem en la validació dels comptes bancaris segons la normativa IBAN.
Codis IBAN i BIC
El codi IBAN (International Bank Account Number) s’utilitza per a identificar a nivell internacional un compte bancari. Sempre comença per dos caràcters que identifiquen el país i va seguit d’una seqüència de dígits. En el cas dels comptes bancaris espanyols, comença per ES i va seguit de 22 dígits, dels quals, els 2 primers són dígits de control i els altres 20 corresponen al CCC (Codi Compte Client) utilitzat des de molt de temps a Espanya.
El codi BIC (Bank Identifier Code) serveix per identificar una entitat bancària.
A la xarxa trobareu gran quantitat de documentació relativa als codis IBAN i BIC.
Fixem-nos en el següent fragment de codi de la classe Python res_partner_bank definida en el mòdul base_iban (fitxer base_iban.py):
_constraints = [ (check_iban, _construct_constraint_msg, ["iban"]), (_check_bank, '\nPlease define BIC/Swift code on bank for bank type IBAN Account to make valid payments', ['bic']) ]
L’anterior atribut _constraints inclou dues restriccions:
- check_iban: aquest és el nom del mètode declarat a la mateixa classe, que s’encarrega de validar el codi IBAN introduït per l’usuari. En cas que sigui erroni, la restricció informa que l’error es produeix en el camp iban i afegeix el missatge que retorna la crida al mètode _construct_constraint_msg (mètode que construeix el missatge ja que és força complex perquè difereix en cada país).
- _check_bank: aquest és el nom del mètode declarat a la mateixa classe que s’encarrega de validar el codi BIC introduït per l’usuari (obligatori en cas que l’usuari hagi indicat que està introduint un codi IBAN). En cas que sigui erroni, la restricció informa que l’error es produeix en el camp bic i mostra el missatge d’error que indica la restricció.
Per comprovar el funcionament d’aquestes restriccions, executeu els següents passos:
- Obriu el manteniment de clients i situeu-vos en un client qualsevol.
- Editeu-lo i aneu a la pestanya Comptabilitat (cal tenir instal·lat el mòdul de comptabilitat). Allà hi veureu una zona de línies anomenada Detalls del banc destinada a incloure els comptes bancaris del client.
- Procediu a donar d’alta un compte bancari amb els següents valors:
Tipus de compte de banc: Compte IBAN (del contrari no fa cap verificació) Número de compte: ES0012341234161234567890 Propietari compte bancari: seleccioneu el client al qui esteu assignant el compte Codi d’identificador bancari: introduïu el text Qualsevol
- Procediu a Desar i Tancar, fet que us portarà de nou a la fitxa del client, on veureu el compte bancari introduït i procediu a intentar enregistrar el client. En aquest moment, OpenERP comprova les restriccions introduïdes i, en aquest cas, com que els codis IBAN i BIC són erronis, apareix la pantalla d’error de la figura. Hi observem dos errors: el primer que ens informa de l’error en validar el camp iban i ens explica com ha de ser el contingut d’aquest camp. El segon ens informa que s’ha produït un error en validar el camp bic.
- Procediu a modificar el valor introduït en el camp iban amb el valor ES7712341234161234567890, que té un format IBAN correcte, tot i que gairebé segur que no existeix realment un CCC de codi 12341234161234567890. Torneu a intentar enregistrar el client. Observareu que ja només apareix l’error relatiu al camp bic.
- Per solucionar l’error del camp bic cal indicar el valor bic d’una de les entitats bancàries donades d’alta a l’empresa activa. Podeu donar d’alta les diverses entitats bancàries a través de l’opció Vendes | Configuració | Llibreta d’adreces | Bancs o, si ho preferiu, instal·leu el mòdul l10n_es_partner (Adaptación de partner para Estado Español) que, entre altres coses, facilita un assistent (Vendes | Configuració | Llibreta d’adreces | Import Bank Data Wizard) que afegeix dades de 191 bancs i caixes espanyoles a partir del registre oficial del Banc d’Espanya. Una vegada indiqueu un codi BIC existent a l’empresa OpenERP, podreu finalitzar l’enregistrament correcte del client.
La vista i el controlador
OpenERP és un programari de gestió empresarial desenvolupat sobre el framework OpenObject de tipus RAD (Rapid Application Development) que facilita una arquitectura MVC (model-vista-controlador) per als desenvolupaments.
Una vegada es domina el disseny del model de dades d’una aplicació desenvolupada sobre OpenObject, com és el cas d’OpenERP, cal entrar en el coneixement del disseny de la vista i del controlador.
En les aplicacions desenvolupades sobre el framework OpenObject, el concepte vista engloba les pantalles que permeten exposar la informació a l’usuari (anomenades vistes o views) per a visualitzar-la i/o editar-la, i els menús, que faciliten un accés organitzat a les vistes. En conseqüència, ens cal aprendre a dissenyar les vistes (views) i els menús.
En el framework OpenObject el disseny del controlador s’efectua amb el llenguatge Python, tot ampliant les classes Python que defineixen el model de dades, amb la incorporació de mètodes.
Els menús i vistes dissenyats en arxius XML, en instal·lar-se en una empresa d’OpenERP, obtenen un identificador numèric dins l’empresa i que OpenERP utilitza per a la gestió de l’aplicació. Aquest identificador acostuma a ser diferent a cada empresa, ja que no totes les empreses tenen els mateixos mòduls instal·lats ni han estat instal·lats en el mateix ordre. A causa que és molt possible que en la fase de disseny el dissenyador hagi de fer referència a menús i vistes es fa necessari un identificador XML per a cada vista/menú que el dissenyador pugui utilitzar. Per això, en la definició XML de menús i vistes s’inclou un identificador (text) que ha de ser únic dins el mòdul i al qual es pot fer referència des del propi mòdul a través del seu nom o des de qualsevol altre mòdul m a través de la sintaxi m.identificador.
Els menús
Les aplicacions desenvolupades amb el framework OpenObject contenen el model, la vista i el controlador seguint el patró de disseny MVC. Dins la vista s’inclouen les diverses pantalles que permeten gestionar la informació i els menús que permeten un accés organitzat a les vistes.
Els menús d’un mòdul OpenObject es dissenyen en arxius XML i han d’estar referenciats des de l’apartat update_xml del fitxer descriptor del mòdul __openerp__.py. Els arxius XML acostumen a incorporar les pantalles (views) i els menús que permeten accedir a les pantalles. Els dos tipus d’elements, però, podrien residir en fitxers XML específics (un per menús i un per views).
Com a exemple, podem analitzar el mòdul school generat per l’eina Dia, en el qual observem la presència del fitxer anomenat school_view.xml:
"update_xml" : ['school_view.xml'],
Els menús d’OpenObject i les seves opcions han de tenir una declaració similar a:
<menuitem id="menuitem_id" name="títol_del_menú" action="action_id" icon="nom_de_icona" web_icon="nom_icona_web" web_icon_hover="nom_icona_web_flotant" parent="menuitem_id" groups="grups_usuaris" sequence="<integer>" />
en la qual els valors específics de cada menú són:
- Atribut id: conté l’identificador XML del menú, únic dins el mòdul.
- Atribut name: conté el títol del menú. Aquest camp és opcional i, si no s’indica, el menú agafarà el nom de l’acció que executi el menú.
- Atribut action: especifica l’identificador de l’acció que executa el menú i que ha d’existir en el mòdul. Quan no s’especifica l’acció s’entén que es tracta de l’arrel d’un menú que conté altres menús i/o opcions. El disseny de la corresponent acció depèn del tipus d’execució associada (obrir una finestra, executar un informe, posar en marxa un assistent…).
- Atribut icon: especifica la icona que acompanya al menú en el client GTK. La icona per defecte és una carpeta. La llista d’icones possibles es pot consultar en el desplegable Icona del manteniment de menús accessible a través de Settings | Personalització | Intefície d’usuari | Elements menú.
- Atribut web_icon: especifica la icona que es mostra en el client web, a la pàgina inicial (Home) .
- Atribut web_icon_hover: especifica la icona que es mostra en el client web, a la pàgina inicial, en passar el ratolí pel damunt. Acostuma a ser la versió acolorida de la icona especificada a l’atribut web_icon.
- Atribut parent: especifica l’identificador del menú pare del qual depèn. Si no es defineix, indica que es tracta d’un menú arrel (menú principal).
- Atribut groups: especifica quin grup d’usuaris pot veure el menú. Si es desitja introduir més d’un grup, cal separar-lo per comes (per exemple, groups="admin,user").
- Atribut sequence: és un enter que s’utilitza per ordenar el menú dins l’arbre de menús, de manera que a major valor, més avall apareix el menú. Aquest valor no és obligatori i per defecte pren el valor 10. Els menús que tinguin el mateix valor s’ordenen per moment temporal de creació.
Les icones dels atributs web_icon o web_icon_hover han de residir a la carpeta del mòdul i el seu nom ha d’anar acompanyat del camí que indiqui la subcarpeta (normalment images o icons) en la qual resideixen (o sense camí si no es troben a cap subcarpeta).
Accions en OpenObject
Les accions defineixen el comportament del sistema en resposta als esdeveniments generats per l’usuari, ja sigui en seleccionar una opció de menú, en prémer un botó, en fer clic damunt un registre…
Hi ha diferents tipus d’accions:
- Window: per obrir una finestra.
- Report: per imprimir un informe.
- Wizard: per iniciar un assistent vinculat a un treball o procés.
- Execute: per executar un mètode en el servidor.
- Group: per reunir un conjunt d’accions en un grup.
Exemple de menús en el mòdul school
Els menús originals del menú school generats per l’eina Dia per a la versió 5.0 d’OpenERP no són vàlids per a la versió 6.1 d’OpenERP, de manera que ens veiem obligats a refer-los segons les normes anteriors. Així, una possibilitat és la següent:
<menuitem name="Courses" id="menu_school"/> <menuitem name="Calendar of Courses" id="menu_school_event" action="action_school_course_event" parent="menu_school"/> <menuitem name="Students" id="menu_school_student" action="action_school_student" parent="menu_school"/> <menuitem name="Configuration" id="menu_school_configuration" parent="menu_school"/> <menuitem name="Courses" id="menu_school_course" action="action_school_course" parent="menu_school_configuration"/> <menuitem name="Professors" id="menu_school_professor" action="action_school_professor" parent="menu_school_configuration"/>
En els elements menuitem anteriors detectem dues definicions de menús i quatre definicions d’opcions que executen accions. Cap dels dos menús incorpora l’atribut icon i, en conseqüència, en el client GTK la icona que els acompanya és una carpeta. El menú principal menu_school tampoc incorpora els atributs web_icon i/o web_icon_hover i, per tant, el mòdul Courses no va acompanyat de cap icona a la pàgina inicial del client web.