view Generic/base.py @ 25:189f8274aecd

gui. switch navigation menu on switch page
author Miguel Ángel Bárcena Rodríguez <miguelangel@obraencurso.es>
date Mon, 20 May 2019 13:18:33 +0200
parents 65e7ae0d0e63
children 16f91684686b
line wrap: on
line source

#!/usr/bin/python
# -*- coding: utf-8 -*-
## File base.py
## This file is part of pyArq-Presupuestos.
##
## Copyright (C) 2010-2019 Miguel Ángel Bárcena Rodríguez
##                         <miguelangel@obraencurso.es>
##
## pyArq-Presupuestos is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## pyArq-Presupuestos is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""base module

In this module are defined the data structures in the
classes:
 * Record: data of each record
 * ParamentricRecord: data of each parametric record
 * Decomposition: data of the decomposition of each record
 * Measure: data of the measure of each record
 * MeasureLine: each measure line data
 * Decimals: data of the decimal places of all the numbers in a budget
 * Sheet: data of the sheet of conditions of a budget
 * Budget: all data of a budget
 * Company: company data
 * Office: company office data
 * File: file data
 * RecordType: Record type data

schema:
 * Budget:
   +-- __records: dictionary records { code : Record }
      * Record:
        +-- code: record code
        +-- synonyms: list of synonym codes 
        +-- hierarchy: A integer number:
            0 -> root
            1 -> Chapter/Subchapter
            2 -> Other
        +-- unit:  unit of measure of the record
        +-- summary: Short description of the record
        +-- prices: List of Prices/Dates
        +-- type
        +-- subtype
            "type" and "subtype":
                0 Without classifying
                   EA  Auxiliary element
                   EU  Unitary element
                   EC  Complex element
                   EF  Functional element
                   OB  Construction site
                   PA  Cost overrun
                   PU  Unitary budget
                1 Labourforce 
                   H   Labourforce
                2 Machinery and auxiliary equipment
                   Q   Machinery
                   %   Auxiliary equipment
                3 Building materials
                   MC  Cement
                   MCr Ceramic
                   MM  Wood
                   MS  Iron and steel
                   ME  Energy
                   MCu Copper
                   MAl Aluminium
                   ML  Bonding agents
                   M   Others materials
                Hierarchy         type  subtype
                0->root         -> 0 -> None,OB
                1->[sub]chapter -> 0 -> None,PU
                2->Other        -> 0 -> None,EA,EU,EC,EF,PA
                                   1 -> None,H
                                   2 -> None,Q,%
                                   3 -> None,MC,MCr,MM,MS,ME,MCu,Mal,ML,M
        +-- parents: List of parent codes
        +-- children: list of Decomposition
           * Decomposition:
             +-- position: Position of the child in the parent descomposition
                 TODO: change this: the position of the record in the budget
             +-- code: child record code
             +-- budget: list of budget and amended budget measures
                * Measure:
                  +-- measure: Total result of measure
                  +-- lines: List of measure lines
                     * MeasureLine:
                       +-- type: Line type:
                            empty string -> Normal
                            1 -> Parcial Subtotal
                            2 -> Accumulated Subtotal
                            3 -> Formula, the comment is a formula.
                       +-- comment: Can be a descriptive text or a formula
                            Valid Operator: '(', ')', '+', '-', '*', '/' and 
                                            '^'
                            Valid variable: 'a', 'b', 'c','d' y 'p'
                                            (Pi=3.1415926)
                       +-- units: Number of Units (a)
                       +-- length: length (b)
                       +-- width: width (c)
                       +-- height: height (d)
                  +-- label: Record Identifiers that are used by some measure
                      programs
                  +-- factor: Factor
                  +-- yield_: Yield
             +-- certification: list of certifications for months measures
                * Measure
             +-- real_cost: list of real cost of construction for months
                 measures
                * Measure
             +-- cost_goals: list of cost goals of construction for months
                 measures
                * Measure
             +-- cost_planned: list of costs planned and amended cost planned
                 measures
                * Measure
        +-- text: Long Description of the record
        +-- sheet: Sheet of conditions object
           * Sheet:
             +-- sheet_dict:
                 { <Field key> : { <Section key> : <Paragraph key>}}
        +-- files:  List of file object
             +-- file
                * Name
                * Type
                * Description
   +-- __synonyms: synonyms dictionary. TODO
   +-- __root: root record code
   +-- __decimals:  List with the decimal places used to round the 
-            result of the calculations with prices and measures
-            The values are Decimals objects 
-            The <0> objets is the default Decimals (seted in FIEBDC-3),
-            The others keys are for the diferent groups of Prices
      * Decimals:
        +-- DN: Number of decimal places of the field "equal-size parts" in the
            measure lines.
            Default: 2 decimal places.
        +-- DD: Number of decimal places of the three dimensions in the 
            measure lines.
            Default: 2 decimal places.
        +-- DS: Number of decimal places of the total sum of a measure.
            Default: 2 decimal places.
        +-- DFP: Number of decimal places of the yield factor in a 
            decomposition of a budget record.
            Default: 3 decimal places.
        +-- DFC: Number of decimal places of the yield factor in a 
            decomposition of a chapter or subchapter, and in its measure lines.
            Dafault: 3 decimal places.
        +-- DFUO: Number of decimal places of the yield factor in a 
            decomposition of a unit of work.
            Default: 3 decimal places.
        +-- DFA: Number of decimal places of the yield factor in a 
            decomposition of a Auxiliary element.
            Default: 3 decimal places.
        +-- DRP: Number of decimal places of the yield in a decomposition
            of a budget record.
            Number of decumal places of the result of the multiplication of 
            the factor and the yield in a decompositon of a budget.
            Default: 3 decimal places.
        +-- DRC: Number of decimal places of the yield (or measure) in a 
            decomposition of a chapter or subchapter.
            Number of decimal places of the result of the multiplictaion of
            the yield (or measure) and the factor in a decomposition of a 
            chapter or subcharter.
            Default: 3 decimal places.
        +-- DRUO: Number of decimal places of the yield in a decomposition of a
            unit of work.
            Decimal places of the result of the multiplication of the yield
            and the factor in a descomposition of a unit of work.
            Default: 3 decimal places.
        +-- DRA: Number of decimal places of the yield in a decompositon of a
            auxiliar element.
            Number of decimal places of the result of the multiplication of 
            the yield and the factor in a descomposition of a auxilar element.
            Default: 3 decimal places.
        +-- DP: Number of decimal places of the price of a budget.
            Default: 2 decimal places.
        +-- DC: Number of decimal places of the price of a chapter or 
            subchapter.
            Default: 2 decimal places.
        +-- DUO: Number of decimal places of the price of a unit of work.
            Default: 2 decimal places.
        +-- DEA: Number of decimal places of the price of a auxiliar element.
            Default: 2 decimal places.
        +-- DES: Number of decimal places of the price of the simple elements.
            Default: 2 decimal places.
        +-- DIR: Number of decimal places of the resulting amount to multiply 
            the total yield and the price of the elements of a unit of work.
            (When there are not measures)
        +-- DIM: Number of decimal places of the resulting amount to multiply 
            the total yield and the price of the elements of a unit of work.
            (When there are measures)
        +-- DIRC: Number of decimal places of the resulting amount to multiply 
            the total yield and the price of the elements of a budget, chapter
            or a subchapter.(When there are not measures)
        +-- DIMC: Number of decimal places of the resulting amount to multiply 
            the total yield and the price of the elements of a budget, chapter
            or a subchapter. (When there are measures)
        +-- DCD: Number of decimal places ot the resulting amount to sum the
            direct costs of a unit of work (and auxiliar element).
            Number of decimal places of the indirect costs.
            Default: 2 decimal places.
        +-- DIVISA: monetary unit.
   +-- __percentages: percentages dictionary:
                      { "CI"  : "",
                        "GG"  : "",
                        "BI"  : "",
                        "BAJA": "",
                        "IVA" : ""}
   +-- __file_owner
   +-- __title_list: titles list: [ "Header", ["Title1", "Title2", ... ] ]
   +-- __title_index: A integer. The active group of Prices and Decimals.
   +-- __sheet_sections: sheet sections dictionary { sheet_code : sheet_title }
   +-- __sheet_fields: sheet fields dictionary { field_code : field_title }
   +-- __sheet_paragraphs: sheet paragraphs dictionary 
                           { paragraph_code : paragraph_text}
   +-- __companys: Dictionary whith companys object
                   { company_code: company_object }
      * Comapany:
        +-- code: company code
        +-- summary: short name
        +-- name: long name
        +-- offices: List of offices:
           * Office:
             +-- type: office type
                    "C"  Central office.
                    "D"  Local Office.
                    "R"  Performer.
             +-- subname: Office name
             +-- address: Ofiice address
             +-- postal_code: postal code
             +-- town: town
             +-- province: province/state
             +-- country: country
             +-- phone: list of phone numbers
             +-- fax: list of fax numbers
             +-- contact_person: Contact person in the office
        +-- cif: CIF
        +-- web: web page
        +-- email: email
   +-- __comment
   +-- __date
   +-- __budgetType" A integer. Type of data in budget
                     0 -> Undefined
                     1 -> Base data.
                     2 -> Budget.
                     3 -> Certificate.
                     4 -> Base date update.
   +-- __budgetCerficateOrder" Only valid if budgetType is 3.
   +-- __budgetCerficateDate" Only valid if budgetType is 3
   +-- __tec_info": Dictionary whith tecnical information
                    {ti_code : ["desciption text", "unit"]}
   +-- __labels": Label dictionary { "label": [ "code", ], }
"""

# Modules
# python 2/3 compatibility
#from __future__ import absolute_import, division, print_function, unicode_literals

import re
import datetime
import os

# pyArq-Presupuestos modules
from Generic import fiebdc
from Generic import utils


# Translatable global Vars

authors = ["Miguel Ángel Bárcena Rodríguez"]
copyright = "Copyright \xc2\xa9 2019 Autoras de Pyarq Presupuestos"
website = "http://pyarq.obraencurso.es/pyarq-Presupuestos"
website_label = "pyarq Presupuestos Web"
comments = _("""
A free program of measurements, budgets and control of construction sites.
In beta development, still there is not a fully functional version.
""")
help = _(
"""
Usage:
    pyarqPresupuestos [file]...

Help Options:
    -h, --help     Show this help
""")

class Record(object):
    """base.Record:
    
    Description:
        Record object
    Constructor:
        base.Record(decimals, code, synonyms, hierarchy, unit, summary,
                 prices, type_, subtype, parents=None, text=None)
    Ancestry:
    +-- object
      +-- Record
    Atributes:
        "code": Write/Read. Code string
        "recordType": Write/Read. RecordType object
        "synonyms": Write/Read. List of synonym codes.
        "parents": Write/Read. List of parent codes
        "children": Write/Read. Decomposition list,
                  list of "Decomposition" instances
        "unit": Write/Read. measure unit of the record
        "summary": Write/Read. Short description of the record
        "prices": Read. List of prices/dates
        "text": Write/Read. Long Description of the record
        "sheet": Write/Read. Sheet of conditions object
        "files": Write/Read. List of file object
        "labels": Write/Read. List of record labels
    Methods:
        isPercentage
        percentageMasq
        hasPercentageMasq(masq)
        {get/set}Code
        {get/set}Synonyms
        {get/set}RecordType
        {get/set}Unit
        {get/set}Summary
        {get/set}Prices
        addPrice
        getPrice
        getAmount
        getDate
        {get/set}Parents
        appendParent
        {get/set}children
        appendChild
        {get/set}Text
        {get/set}Sheet
        {get/set}Files
        addFile
        {get/set}Labels
        addLabel
        getChildPositions:
    """
    __slots__ = ["_Record__code", "_Record__synonyms",
                 "_Record__recordType", "_Record__unit",
                 "_Record__summary", "_Record__prices",
                 "_Record__parents", "_Record__children",
                 "_Record__text", "_Record__sheet",
                 "_Record__files", "_Record__labels"]

    def __getstate__(self):
        return (self.__code, self.__synonyms, self.__recordType,
                self.__unit, self.__summary, self.__prices,
                self.__parents, self.__children, self.__text,
                self.__sheet, self.__files, self.__labels)

    def __setstate__(self, tuple):
        self.__code = tuple[0]
        self.__synonyms = tuple[1]
        self.__recordType = tuple[2]
        self.__unit = tuple[3]
        self.__summary = tuple[4]
        self.__prices = tuple[5]
        self.__parents = tuple[6]
        self.__children = tuple[7]
        self.__text = tuple[8]
        self.__sheet = tuple[9]
        self.__files = tuple[10]
        self.__labels = tuple[11]

    def __init__(self, decimals, code, synonyms, hierarchy, unit, summary,
                 prices, type_, subtype, parents=None, text=None):
        self.code = code
        self.synonyms = synonyms
        self.recordType = (hierarchy, type_, subtype)
        self.unit = unit
        self.summary = summary
        self.setPrices(prices, decimals)
        if parents is None:
            parents = []
        self.parents = parents
        self.children = []
        if text is None:
            text = ""
        self.text = text
        self.sheet = Sheet()
        self.files = []
        self.labels = []

    def isPercentage(self):
        if "%" in self.__code or "&" in self.__code:
            return True
        else:
            return False

    def percentageMasq(self):
        if "%" in self.__code:
            return self.__code.split("%")[0]
        elif "&" in self.__code:
            return self.__code.split("&")[0]

    def hasPercentageMasq(self, masq):
        if len(self.__code) >= len(masq):
            _part_code = self.__code[:len(masq)]
            if _part_code == masq:
                return True
        return False

    def getCode(self):
        return self.__code

    def setCode(self, code):
        """setCode(code)
        
        Sets the code, must be a valid code
        """
        if not utils.is_valid_code(code)[0]:
            raise ValueError(utils.mapping(_("Invalid code: $1"),(str(code),)))
        self.__code = code

    def getSynonyms(self):
        return self.__synonyms

    def setSynonyms(self,synonyms):
        """setSynonyms(synonyms)
        
        Sets the synonyms codes of the record.
        synonyms must fulfill:
            - must be a list
            - the items must be valid codes
        """
        if not isinstance(synonyms, list):
            raise TypeError( utils.mapping(_("Synonyms ($1) must be a list, " \
                  "code: $2"), (str(synonyms), str(self.__code))) )
        for code in synonyms:
            if not utils.is_valid_code(code)[0]:
                raise ValueError( utils.mapping(_("Invalid Code in synomyms "\
                      "list ($1) code: $2"), (str(code), str(self.__code))) )
        self.__synonyms = synonyms

    def getRecordType(self):
        return self.__recordType

    def setRecordType(self, recordType):
        """setRecordType(recordType)
        
        Set the record type.
        recordType (hierarchy, type,subtype)
        
        hierarchy must be -1, 0, 1 or 2
        type must be 0, 1, 2, 3 or a empty string
        subtype must be EA, EU, EC, EF, OB, PA, PU, H, Q, %, MC, MCr, 
                MM, MS, ME, MCu, MAl, ML, M, or a empty string
        """
        _recordType = RecordType(recordType[0],recordType[1],recordType[2])
        self.__recordType = _recordType

    def getUnit(self):
        return self.__unit

    def setUnit(self,unit):
        """setUnit(unit)
        
        Set the unit of measure
        The unit must be a string.
        """
        if not isinstance(unit, str):
            raise TypeError( utils.mapping(_("Unit ($1) must be a string: $2"),
                  (str(unit), str(self.__code))) )
        self.__unit = unit

    def getSummary(self):
        return self.__summary

    def setSummary(self,summary):
        """setSummary(summary)
        
        Set the summary of a record
        The summary must be a string.
        """
        if not isinstance(summary, str):
            raise TypeError( utils.mapping(_("Summary ($1) must be a string: "\
                  "$1"), (str(summary), str(self.__code))) )
        self.__summary = summary

    def getPrices(self):
        return self.__prices

    def setPrices(self, prices, decimals):
        """setPrice(prices, decimals)
        
        Set the price list of the record.
        prices must fulfill:
            - it must be a list
            - the items must be a list with two items
            - the first item: price must be a float
        """
        if not isinstance(prices, list):
            raise TypeError( utils.mapping(_("Prices ($1) must be a list: $2"),
                  (str(prices), str(self.__code))) )
        for index in range(len(prices)):
            _price_date = prices[index]
            _price_date = self._validate_price_date(_price_date, decimals)
            prices[index] = _price_date
        self.__prices = prices

    def addPrice(self, price_date, decimals):
        """addPrice(price_date, decimals)
        
        Add a price to the price list of the record.
        price must fulfill:
            - must be a list with two items
            - the first item: price must be a float
        """
        price_date = self._validate_price_date(price_date, decimals)
        self.__prices.append(price_date)

    def _validate_price_date(self, price_date, decimals):
        if not isinstance(price_date, list) and len(price_date) == 2:
            raise ValueError( utils.mapping(_("Price ($1) must be a list"\
                  " with two items: $2"), (str(price_date), str(self.__code))) )
        _price = price_date[0]
        _date = price_date[1]
        if not isinstance(_price, float):
            raise TypeError( utils.mapping(_("Price must be a float "\
                      "number: $1"), (str(_price),)) )
        _D = abs(decimals.getD(self.recordType))
        _price = round(_price, _D)
        price_date[0] = _price
        # TODO: validate date
        return price_date

    def getPrice(self, index_price):
        if len(self.__prices) <= index_price:
            raise IndexError( _("The record do not have this Price. Code: %s"
                                % self.__code) )
        return self.__prices[index_price][0]

    def getDate(self, index_price):
        if len(self.__prices) <= index_price:
            raise IndexError( _("The record do not have this Price") )
        return self.__prices[index_price][1] 

    def getParents(self):
        return self.__parents

    def setParents(self,parents):
        """setParents(parents)
        
        Sets the list of parents codes of the record.
        parents must fulfill
            - it must be a list
            - the items must be valid codes
        """
        if not isinstance(parents, list):
            raise TypeError( utils.mapping(_("Parents ($1) must be a list: $2"),
                  (str(parents), str(self.__code))) )
        for parent in parents:
            if not utils.is_valid_code(parent)[0]:
                raise ValueError(utils.mapping(_("Invalid parent code ($1) " \
                      "in the record: $2"), (str(padre), str(self.__code))) )
        self.__parents = parents

    def appendParent(self, parent):
        """appendParent(parent)
        
        parent must be a valid code
        Append a parent to the list of parents codes of the record.

        """
        if not utils.is_valid_code(parent)[0]:
            raise ValueError( utils.mapping(_("Invalid parent code ($1) " \
                  "in the record: $2"), (str(parent), str(self.__code))) )
        self.__parents.append(parent)

    def getchildren(self):
        return self.__children

    def setchildren(self,children):
        """setchildren(children)
        
        Sets the list of children of a record
        children must fulfill
            - it must be a list
            - the items must be instances of Decomposition class
        """
        if not isinstance(children, list):
            raise TypeError( utils.mapping(_("children ($1) must be a list, "\
                  "record: $2"), (str(children), str(self.__code))) )
        for _child in children:
            if not isinstance(_child, Decomposition):
                raise ValueError( utils.mapping(_("child ($1) must be a "\
                      "Decomposition object, record: $2"),
                      (str(_child), str(self.__code))) )
            _record_code = self.code
            for _measure_list in [_child.budgetMeasures, _child.certification,
                                  _child.real_cost, _child.cost_goals,
                                  _child.cost_planned]:
                if isinstance(_measure_list, list):
                    for _measure in _measure_list:
                        _measurerecordCode = _record_code
        self.__children = children

    def appendChild(self, child_code, decimals, factor=0.0, yield_=0.0,
                    measure=0.0, measure_list=None, type_=None, label=None):
        """appendChildren(child_code, factor=0.0, yield_=0.0,
                    measure=0.0, measure_list=None, type_=None, label=None))
        
        position:
        child_code:
        factor:
        yield_:
        measure:
        measure_list:
        type_:
        label:
        
        Append a child to the list of children
        """
        if measure_list is None:
            measure_list = []
        if type_ is None:
            type_ = ""
        if label is None:
            label = ""
        _measure = Measure(decimals, self.recordType,
                           measure, [], label, factor, yield_)
        if len(measure_list) > 0:
            _measure.buildMeasure( measure_list, type_, decimals,
                                 self.recordType)
        _position = len(self.__children)
        _child = Decomposition(_position, child_code, [_measure])
        self.__children.append(_child)
        return _child

    def getText(self):
        return self.__text

    def setText(self,text):
        """setText(text)
        
        Sets the text of the record
        It must be a string
        """
        if not isinstance(text, str):
            raise TypeError( utils.mapping(_("Text ($1) must be a string, "\
                  "record: $2"), (str(text), str(self.__code))) )
        self.__text = text

    def getSheet(self):
        return self.__sheet

    def setSheet(self, sheet):
        """setSheet(sheet)
        
        Sets the sheet of condition object
        """
        if not isinstance(sheet, Sheet):
            raise ValueError( _("sheet must be a Sheet instance") )
        self.__sheet = sheet

    def getFiles(self):
        return self.__files

    def setFiles(self, files):
        """setFiles(files)
        
        Sets the files list
        """
        if not isinstance(files, list):
            raise ValueError( utils.mapping(_("files must be a list: $1"),
                                              str(files)) )
        _files = []
        for file in files:
            if isinstance(file, File):
                _files.append(file)
            elif isinstance(file, list):
                _file_path = file[0]
                _type = file[1]
                _description = file[2]
                if not os.path.exists(file[0]):
                    raise ValueError( _("Incorrect path") )
                _file = File(file_path, type_, description)
                _files.append(_file)
            else:
                raise ValueError( utils.mapping(_(
                      "file must be a list or a File object: $1"),str(file)) )
        self.__files = _files

    def addFile(self, file_path, type_, description):
        """addFile(file_path, type_, description)
        
        Add a file to a record instance
        """
        if not os.path.exists(file_path):
            raise ValueError( _("Incorrect path") )
        _name = os.path.basename(file_path)
        _isin = False
        for _ofile in self.__files:
            if _ofile.name == _name:
                _isin = True
        if not _isin:
            _file = File(_name, type_, description)
            self.__files.append(_file)

    def getLabels(self):
        return self.__labels

    def setLabels(self, labels):
        """setLabels(labels)
        
        Sets the labels list of a record
        """
        if not isinstance(labels, list):
            raise ValueError( _("labels must be a list") )
        _labels = []
        for _label in labels:
            if isinstance(_label, str):
                _labels.append(_label)
            else:
                raise ValueError( _("label must be a string") )
        self.__labels = _labels

    def addLabel(self, label):
        """addLabel(label)
        
        Add a label to a record instance
        """
        if not isinstance(label, str):
            raise ValueError( _("Label must be a string") )
        if not label in self.__labels:
            self.__labels.append(label)

    def getChildPositions(self, child_code):
        """getChildPath(child_code):
        
        Try to return positions of a childcode
        """
        children = self.children
        positions = []
        for child in children:
            if child.code == child_code:
                positions.append(child.position)
        return positions

    recordType = property(getRecordType, setRecordType, None,
        """Record Type object
        """)
    code = property(getCode, setCode, None,
        """Record code
        """)
    synonyms = property(getSynonyms, setSynonyms, None,
        """List of codes synonyms of the code
        """)
    unit = property(getUnit,setUnit, None,
        """Measure Unit of the record
        """)
    summary = property(getSummary, setSummary, None, 
        """Short description of the record
        """)
    prices = property(getPrices, None, None, 
        """List of Price/Date
        """)
    parents = property(getParents, setParents, None,
        """List of codes of the records which the record is in 
        its decomposition
        """)
    children = property(getchildren, setchildren, None,
        """List of Decompositon intances""")
    text = property(getText, setText, None,
        """Long description of the record""")
    sheet = property(getSheet, setSheet, None,
        """Sheet of conditions object""")
    files = property(getFiles, setFiles, None,
        """File list""")
    labels = property(getLabels, setLabels, None,
        """Label list""")


class ParametricRecord(Record):
    """base.ParametricRecord:
    
    Description:
        Parametric Record object
    Constructor:
        base.ParametricRecord(budget, code, synonyms, hierarchy, unit, summary,
                 prices, type_, subtype, parents=None, text=None)
    Ancestry:
    +-- object
      +-- Record
        +-- ParametricRecord
    Atributes:

    Methods:

    """

    __slots__ = ["_ParametricRecord__budget",
                 "_ParametricRecord__code", "_ParametricRecord__synonyms",
                 "_ParametricRecord__hierarchy", "_ParametricRecord__unit",
                 "_ParametricRecord__summary", "_ParametricRecord__prices",
                 "_ParametricRecord__type", "_ParametricRecord__subtype",
                 "_ParametricRecord__parents", "_ParametricRecord__children",
                 "_ParametricRecord__text", "_ParametricRecord__sheet",
                 "_ParametricRecord__files", "_ParametricRecord__labels",
                 "_ParametricRecord__parameters",
                 "_ParametricRecord__select_comment", 
                 "_ParametricRecord__vars",
                 "_ParametricRecord__parametric_summary",
                 "_ParametricRecord__parametric_text",]

    def __getstate__(self):
        return (self.__budget, self.__code, self.__synonyms, self.__hierarchy,
                self.__unit, self.__summary, self.__prices, self.__type,
                self.__subtype, self.__parents, self.__children, self.__text,
                self.__sheet, self.__files, self.__labels, self.__parameters,
                self.__select_comment, self.__vars,
                self.__parametric_summary, self.__parametric_text)

    def __setstate__(self, tuple):
        self.__budget = tuple[0]
        self.__code = tuple[1]
        self.__synonyms = tuple[2]
        self.__hierarchy = tuple[3]
        self.__unit = tuple[4]
        self.__summary = tuple[5]
        self.__prices = tuple[6]
        self.__type = tuple[7]
        self.__subtype = tuple[8]
        self.__parents = tuple[9]
        self.__children = tuple[10]
        self.__text = tuple[11]
        self.__sheet = tuple[12]
        self.__files = tuple[13]
        self.__labels = tuple[14]
        self.__parameters = tuple[15]
        self.__select_comment = tuple[16]
        self.__vars = tuple[17]
        self.__parametric_summary = tuple[18]
        self.__parametric_text = tuple[19]

    def __init__(self, budget, code, synonyms, hierarchy, unit, summary,
                 prices, type_, subtype, parents=None, text=None):
        if parents is None:
            parents = []
        if text is None:
            text = ""
        Record.__init__(self, budget, code, synonyms, hierarchy, unit, summary,
                 prices, type_, subtype, parents, text)
        self.__parameters = {}
        self.__select_comment = ""
        self.__vars = {}
        self.parametric_summary = ""
        self.parametric_text = ""

    def getParameter(self, parameter):
        if parameter in self.__parameters:
            return self.__parameters[parameter]
        else:
            return None

    def setParameter(self, parameter, parameter_list):
        self.__parameters[parameter] = parameter_list

    def getSelectComment(self):
        return self.__select_comment

    def setSelectComment(self, select_comment):
        self.__select_comment = select_comment

    def getVar(self, var):
        if var in self.__vars:
            return self.__vars[var]
        else:
            return None

    def setVar(self, var, var_list):
        self.__vars[var] = var_list

    def getParametricSummary(self):
        return self.__parametric_summary

    def setParametricSummary(self, parametric_summary):
        self.__parametric_summary = parametric_summary

    def getParametricText(self):
        return self.__parametric_text

    def setParametricText(self, parametric_text):
        self.__parametric_text = parametric_text

    parameter = property(getParameter, setParameter, None,
        """Record parameter
        """)
    select_comment = property(getSelectComment, setSelectComment, None,
        """Seclect comment
        """)
    var = property(getVar, setVar, None,
        """Record var
        """)
    parametric_summary = property(getParametricSummary, setParametricSummary,
        None,
        """Parametric summary
        """)
    parametric_text = property(getParametricText, setParametricText, None,
        """Seclect comment
        """)


class Decomposition(object):
    """base.Decomposition:
    
    Description:
        Decomposition object
    Constructor:
        base.Decomposition(position, code, budgetMeasures, certification=None,
                 real_cost=None, cost_goals=None, cost_planned=None)
    Ancestry:
    +-- object
      +-- Decomposition
    Atributes:
        "position": the position of the child record in the parent record
        "code": Record code.
        Measures:
        "budgetMeasures": list of budget and Amended budget measures
        "certification": list of certifications for months measures
        "real_cost": list of real cost of construction for months measures
        "cost_goals": list of cost goals of construction for months measures
        "cost_planned": list of costs planned and amended cost planned measures
    Methods:
        {get/set}position
        {get/set}Code
        {get/set}BudgetMeasures
        {get/set}Certification
        {get/set}RealCost
        {get/set}CostGoals
        {get/set}CostPlanned
    """

    __slots__ = ["_Decomposition__position",
                 "_Decomposition__code",
                 "_Decomposition__budgetMeasures",
                 "_Decomposition__certification",
                 "_Decomposition__real_cost",
                 "_Decomposition__cost_goals",
                 "_Decomposition__cost_planned",
                ]

    def __getstate__ (self):
        return (self.__position, self.__code, self.__budgetMeasures,
                self.__certification, self.__real_cost, self.__cost_goals,
                self.__cost_planned)

    def __setstate__(self,tuple):
        self.__position = tuple[0]
        self.__code = tuple[1]
        self.__budgetMeasures = tuple[2]
        self.__certification = tuple[3]
        self.__real_cost = tuple[4]
        self.__cost_goals = tuple[5]
        self.__cost_planned = tuple[6]

    def __init__(self, position, code, budgetMeasures, certification=None,
                 real_cost=None, cost_goals=None, cost_planned=None):
        self.position = position
        self.code = code
        self.budgetMeasures = budgetMeasures
        self.certification = certification
        self.real_cost = real_cost
        self.cost_goals = cost_goals
        self.cost_planned = cost_planned

    def getPosition(self):
        return self.__position

    def setPosition(self, position):
        if not isinstance(position, int):
            raise ValueError( _("Position must be a integer") )
        self.__position = position

    def getCode(self):
        return self.__code

    def setCode(self, code):
        self.__code = code

    def getBudgetMeasures(self):
        return self.__budgetMeasures

    def setBudgetMeasures(self, budgetMeasures):
        if not isinstance(budgetMeasures, list):
            raise ValueError( _("BudgetMeasures atribute must be a list") )
        for _measure in budgetMeasures:
            if not isinstance(_measure, Measure):
                raise ValueError( _("BudgetMeasures item must be a Measure "/
                                    "object") )
        self.__budgetMeasures = budgetMeasures

    def getCertification(self):
        return self.__certification

    def setCertification(self, certification):
        if not (certification is None or isinstance(certification, list)):
            raise ValueError(_("Certification atribute must be a list or None"))
        self.__certification = certification

    def getRealCost(self):
        return self.__real_cost

    def setRealCost(self, real_cost):
        if not (real_cost is None or  isinstance(real_cost, list)):
            raise ValueError( _("Real cost atribute must be a list or None") )
        self.__real_cost = real_cost

    def getCostGoals(self):
        return self.__cost_goals

    def setCostGoals(self, cost_goals):
        if not (cost_goals is None or  isinstance(cost_goals, list)):
            raise ValueError( _("Cost goals atribute must be a list or None") )
        self.__cost_goals = cost_goals

    def getCostPlanned(self):
        return self.__cost_planned

    def setCostPlanned(self, cost_planned):
        if not (cost_planned is None or  isinstance(cost_planned, list)):
            raise ValueError(_("Cost Planned atribute must be a list or None"))
        self.__cost_planned = cost_planned

    position = property(getPosition, setPosition, None,
        """Postion of the record in the budget
        """)
    code = property(getCode, setCode, None,
        """Record code
        """)
    budgetMeasures = property(getBudgetMeasures, setBudgetMeasures, None, 
        """list of budget and Amended budget measures
        """)
    certification = property(getCertification, setCertification,None, 
        """ list of certifications by months measures
        """)
    real_cost = property(getRealCost, setRealCost, None,
        """ list of real cost of construction for months measures
        """)
    cost_goals = property(getCostGoals, setCostGoals, None,
        """ list of cost goals of construction for months measures
        """)
    cost_planned = property(getCostPlanned, setCostPlanned, None,
        """ list of costs planned and amended cost planned measures
        """)


class Measure(object):
    """base.Measure:
    
    Description:
        Measure object
    Constructor:
        base.Measure(decimals, recordType, measure, lines,
                     label, factor, yield_)
    Ancestry:
    +-- object
      +-- Measure
    Atributes:
        "measure": Total result of measure.
        "lines": List of measure lines, List of LineM instances.
        "label": Record Identifiers that are used by some measure programs.
        "factor":
        "yield":
        "fixed": If fixed is True the yield is not calculated from measure
    Methods:
        getMeasure()
        setMeasure(measure, decimals)
        {get/set}Lines
        {get/set}Label
        getFactor()
        setFactor(factor, decimals, recordType)
        getYield()
        setYield(yield_, decimals, recordType)
        getFixed()
        setFixed(decimals)
        buildMeasure(list_lines, type, decimals)
        calculateMeasure(decimals)
        updateYield(decimals)
    """

    __slots__ = ["_Measure__measure",
                 "_Measure__lines",
                 "_Measure__label",
                 "_Measure__factor",
                 "_Measure__yield_",
                 "_Measure__fixed"]

    def __getstate__ (self):
        return (self.__measure, self.__lines, self.__label,
                self.__factor, self.__yield_, self.__fixed)

    def __setstate__(self,tuple):
        self.__measure = tuple[0]
        self.__lines = tuple[1]
        self.__label = tuple[2]
        self.__factor = tuple[3]
        self.__yield_ = tuple[4]
        self.__fixed = tuple[5]

    def __init__(self, decimals, recordType, measure, lines,
                 label, factor, yield_):
        self.setMeasure(measure, decimals)
        self.lines = lines
        self.label = label
        self.setFactor(factor, decimals, recordType)
        self.setYield(yield_, decimals, recordType)
        self.__fixed = False

    def getMeasure(self):
        return self.__measure

    def setMeasure(self, measure, decimals):
        if not isinstance(measure, float):
            raise ValueError( utils.mapping(_("Measure must be a float "\
                  "number. Type: $1"), (type(measure),)) )
        # TODO: test after
        _DS = abs(decimals.DS)
        measure = round(measure, _DS)
        self.__measure = measure

    def getLines(self):
        return self.__lines

    def setLines(self, lines):
        if not isinstance(lines, list):
            raise ValueError( _("Lines must be a list") )
        for _line in lines:
            if not isinstance(_line, MeasureLine):
                raise ValueError( _("Line must be a MeasureLine objetc") )
        self.__lines = lines

    def getLabel(self):
        return self.__label

    def setLabel(self, label):
        self.__label = label

    def setFactor(self, factor, decimals, recordType):
        if not isinstance(factor, float):
            raise ValueError( utils.mapping(_("Factor must be a float number "\
                  "|$1|"), (str(factor),)) )
        # TODO: test after
        _DF = abs(decimals.getDF(recordType))
        factor = round(factor, _DF)
        self.__factor = factor

    def getFactor(self):
        return self.__factor

    def setYield(self, yield_, decimals, recordType):
        if not isinstance(yield_, float):
            raise ValueError( _("Yield must be a float number") )
        # TODO: test after
        _DR = abs(decimals.getDR(recordType))
        yield_ = round(yield_, _DR)
        self.__yield_ = yield_

    def getYield(self):
        return self.__yield_

    def setFixed(self, fixed, decimals):
        if not isinstance(fixed, bool):
            raise ValueError( _("Fixed must be boolean object") )
        self.__fixed = fixed
        self.updateYield(decimals)

    def getFixed(self):
        return self.__fixed

    measure = property(getMeasure, None, None,
    """Total result of the measure
    """)
    lines = property(getLines, setLines, None,
    """List of measure lines, List of "MeasureLine" instances
    """)
    label = property(getLabel, setLabel, None,
    """Record identifiers that are used in some measure programs
    """)
    factor = property(getFactor, None, None, 
        """Factor
        """)
    yield_ = property(getYield, None, None, 
        """Yield of a record
        """)
    fixed = property(getFixed, setFixed,None, 
        """If fixed is True the yield is not calculated from measure
        """)

    def buildMeasure(self, list_lines, type_, decimals, recordType):
        """setMeasure(list_lines, type_, decimals, recordType)
        
        list_lines: list of measure lines
            [ [linetype, comment, units, length, width, height, formula], ... ]
            linetype:
                #-#empty string -> Normal
                0 -> Normal
                1 -> Parcial Subtotal
                2 -> Accumulated Subtotal
                3 -> Formula
            comment: comment string
            units: Number of Units (a)
            length: Length (b)
            width: Width (c)
            height: Height (d)
            formula: Can be a formula or a empty string
                Valid Operator: '(', ')', '+', '-', '*', '/' and '^'
                Valid variable: 'a', 'b', 'c','d' and 'p' (Pi=3.1415926)
        type: type of action
            M: Set measure
            A: Add measure
        decimal: budget decimals object
        
        Sets the measurelines for a record
        """
        # TODO: calcutate measure from lines
        _parcial = 0
        _total = 0
        _lines = []
        for _line in list_lines:
            _type, _comment = _line[0], _line[1]
            _units, _length = _line[2], _line[3]
            _width, _height = _line[4], _line[5]
            _formula = _line[6]
            _measure_line = MeasureLine(decimals, _type, _comment, _units,
                                        _length, _width, _height, _formula)
            _lines.append(_measure_line)

        if type_ == "M":
            self.lines = _lines
        elif type_ == "A":
            self.lines.extend(_lines)
        else:
            raise ValueError( utils.mapping(_("Type must be M or A. Type: $1"),
                                            (str(type_),)) )
        self.calculateMeasure(decimals, recordType)

    def calculateMeasure(self, decimals, recordType):
        #TODO: round acumulated_subtotal and parcial_subtotal
        if len(self.lines) > 0:
            _acumulated_total = 0.0
            _parcial_total = 0.0
            for line in self.lines:
                _parcial = line.parcial
                _acumulated_total += _parcial
                if line.lineType == 2:
                    line.setAcumulatedSubtotal(_acumulated_total, decimals)
                elif line.lineType == 1:
                    _parcialSubtotal = _acumulated_total - _parcial_total
                    line.setParcialSubtotal(_parcialSubtotal, decimals)
                    _parcial_total = _acumulated_total
            self.setMeasure(_acumulated_total, decimals)
            _DR = abs(decimals.getDR(recordType))
            self.updateYield(decimals, recordType)

    def updateYield(self, decimals, recordType):
        if not self.fixed:
            self.setYield(self.measure, decimals, recordType)

class MeasureLine(object):
    """base.MeasureLine:
    
    Description:
        MeasureLine object
    Constructor:
        base.MeasureLine(budget, type_, comment, units, length, width, height,
                         formula)
    Ancestry:
    +-- object
      +-- MeasureLine
    Atributes:
        "lineType": Line type:
            #-#empty string -> Normal
            0 -> Normal
            1 -> Parcial Subtotal
            2 -> Accumulated Subtotal
            3 -> Formula, the comment is a formula.
        "comment": Descriptive text string
        "units": Number of Units (a)
        "length": length (b)
        "width": Width (c)
        "height": Height (d)
        "formula": can be a valid formula or a empty string
            Valid Operator: '(', ')', '+', '-', '*', '/' and '^'
            Valid variable: 'a', 'b', 'c','d'y 'p' (Pi=3.1415926)
        "partial" : result of measure line
        "parcial_subtotal"
        "acumulated_subtotal"
    Methods:
        {get/set}LineType
        {get/set}Comment
        {get/set}Units
        {get/set}Length
        {get/set}Width
        {get/set}Height
        {get/set}Formula
        getParcial
        {get/set}ParcialSubtotal
        {get/set}AcumulatedSubtotal
        calculateParcial
        eval_formula
    """

    __slots__ = ["_MeasureLine__lineType",
                 "_MeasureLine__comment",
                 "_MeasureLine__units",
                 "_MeasureLine__length",
                 "_MeasureLine__width",
                 "_MeasureLine__height",
                 "_MeasureLine__formula",
                 "_MeasureLine__parcial",
                 "_MeasureLine__parcial_subtotal",
                 "_MeasureLine__acumulated_subtotal",
                ]

    def __getstate__ (self):
        return (self.__lineType, self.__comment, self.__units,
                self.__length, self.__width, self.__height, self.__formula,
                self.__parcial)

    def __setstate__(self,tuple):
        self.__lineType = tuple[0]
        self.__comment = tuple[1]
        self.__units = tuple[2]
        self.__length = tuple[3]
        self.__width = tuple[4]
        self.__height = tuple[5]
        self.__formula = tuple[6]
        self.__parcial = tuple[7]
        #self.calculateParcial()

    def __init__(self, decimals, type_, comment, units, length, width, height,
                 formula):
        self.__parcial = 0.0
        self.__parcial_subtotal = 0.0
        self.__acumulated_subtotal = 0.0
        self.lineType = type_
        self.comment = comment
        self.setUnits(units, decimals)
        self.setLength(length, decimals)
        self.setWidth(width, decimals)
        self.setHeight(height, decimals)
        self.setFormula(formula, decimals)
        #self.calculateParcial()

    def getLineType(self):
        return self.__lineType

    def getComment(self):
        return self.__comment

    def getUnits(self):
        return self.__units

    def getLength(self):
        return self.__length

    def getWidth(self):
        return self.__width

    def getHeight(self):
        return self.__height

    def getFormula(self):
        return self.__formula

    def getParcial(self):
        return self.__parcial

    def getParcialSubtotal(self):
        return self.__parcial_subtotal

    def getAcumulatedSubtotal(self):
        return self.__acumulated_subtotal

    def setParcialSubtotal(self, parcial_subtotal, decimals):
        if not isinstance(parcial_subtotal, float):
            raise ValueError( utils.mapping(_(" Parcial Subtotal must be a "\
                      "float number. Parcial: $1"), (str(parcial_subtotal),)) )
        _DS = abs(decimals.DS)
        parcial_subtotal = round(parcial_subtotal, _DS)
        self.__parcial_subtotal = parcial_subtotal

    def setAcumulatedSubtotal(self, acumulated_subtotal, decimals):
        if not isinstance(acumulated_subtotal, float):
            raise ValueError( utils.mapping(_(" Acumulated Subtotal must be "\
                      "a float number. Parcial: $1"),
                     (str(acumulated_subtotal),)) )
        _DS = abs(decimals.DS)
        acumulated_subtotal = round(acumulated_subtotal, _DS)
        self.__acumulated_subtotal = acumulated_subtotal

    def calculateParcial(self, decimals):
        _DS = abs(decimals.DS)
        if self.lineType == 1 or self.lineType == 2:
            _parcial = 0.0
        elif self.lineType == 0: # self.formula == "":
            if isinstance(self.units, float):
                _a = self.units
            else:
                _a = 0.0
            if isinstance(self.length, float):
                _b = self.length
            else:
                _b = 1.0
            if isinstance(self.width, float):
                _c = self.width
            else:
                _c = 1.0
            if isinstance(self.height, float):
                _d = self.height
            else:
                _d = 1.0
            _parcial =  _a * _b  * _c * _d
        else:
            _parcial = self.eval_formula()
        _parcial = round(_parcial, _DS)
        self.__parcial = _parcial

    def setLineType(self, type_):
        if not type_ in [0, 1, 2, 3]:
            raise ValueError( utils.mapping(_("Invalid measure line type ($1)"),
                  (str(type_),)) )
        self.__lineType = type_

    def setComment(self, comment):
        if not isinstance(comment, str):
            raise ValueError( utils.mapping(_("Measure Comment must be a "\
                  "string ($1)"), (str(comment),)) )
        self.__comment = comment

    def setUnits(self, units, decimals):
        if units != "":
            if not isinstance(units, float):
                raise ValueError( utils.mapping(_("Invalid Measure Units ($1)"),
                      (str(units),)) )
            _DN = abs(decimals.DN)
            units = round(units, _DN)
        self.__units = units
        try:
            self.calculateParcial(decimals)
        except AttributeError:
            pass

    def setLength(self, length, decimals):
        if length != "":
            if not isinstance(length, float):
                raise ValueError( utils.mapping(_("Invalid Measure length ($1)"),
                      (str(units),)) )
            _DD = abs(decimals.DD)
            length = round(length, _DD)
        self.__length = length
        try:
            self.calculateParcial(decimals)
        except AttributeError:
            pass

    def setWidth(self, width, decimals):
        if width != "":
            if not isinstance(width, float):
                raise ValueError( utils.mapping(_("Invalid Measure Width ($1)"),
                      (str(units),)) )
            _DD = abs(decimals.DD)
            width = round(width, _DD)
        self.__width = width
        try:
            self.calculateParcial(decimals)
        except AttributeError:
            pass

    def setHeight(self, height, decimals):
        if height != "":
            if not isinstance(height, float):
                raise ValueError( utils.mapping(_("Invalid Measure Height ($1)"),
                      (str(height),)) )
            _DD = abs(decimals.DD)
            height = round(height, _DD)
        self.__height = height
        try:
            self.calculateParcial(decimals)
        except AttributeError:
            pass

    def setFormula(self, formula, decimals):
        if not isinstance(formula, str):
            raise ValueError( utils.mapping(_("Formula must be a "\
                  "string ($1)"), (str(formula),)) )
        if re.match(".*[^0123456789\.()\+\-\*/\^abcdp ].*", formula):
            raise ValueError( utils.mapping(_("There is invalid characters"\
                  "in formula ($1)"), (str(formula),)) )
        self.__formula = formula
        try:
            self.calculateParcial(decimals)
        except AttributeError:
            pass

    lineType = property(getLineType, setLineType, None,
    """Type of measure line
    """)
    comment = property(getComment, setComment, None,
    """Text
    """)
    units = property(getUnits, None, None,
    """Number of units
    """)
    length = property(getLength, None, None,
    """Length measure
    """)
    width = property(getWidth, None, None,
    """Width measure
    """)
    height = property(getHeight, None, None,
    """Height measure
    """)
    formula = property(getFormula, None, None,
    """Formula
    """)
    parcial = property(getParcial, None, None,
    """result of measure line
    """)
    acumulated_subtotal = property(getAcumulatedSubtotal,
                                   None, None,
    """Acumulated subtotal
    """)
    parcial_subtotal = property(getParcialSubtotal,
                                None, None,
    """Parcial subtotal
    """)

    def eval_formula(self):
        """eval_formula()
        
        formula:
            Valid Operator: '(', ')', '+', '-', '*', '/' and '^'
            Valid variable: 'a', 'b', 'c','d'y 'p' (Pi=3.1415926)
        units: Number of Units (a)
        length: Length (b)
        width: Width (c)
        height: Height (d)

        Evals the formula and return the result
        """
        formula = self.formula
        a = self.units
        b = self.length
        c = self.width
        d = self.height
        if a == "": a = 0.0
        if b == "": b = 0.0
        if c == "": c = 0.0
        if d == "": d = 0.0
        try:
            a = float(a)
        except:
            raise ValueError( _("'a' value must be a float number") )
        try:
            b = float(b)
        except:
            raise ValueError( _("'b' value must be a float number") )
        try:
            c = float(c)
        except:
            raise ValueError( _("'c' value must be a float number") )
        try:
            d = float(d)
        except:
            raise ValueError( _("'d' value must be a float number") )
        # spaces are erased
        formula.replace(" ","")
        # operators and varibles are replaced
        formula = formula.replace("+", " + ")
        formula = formula.replace("-", " - ")
        formula = formula.replace("*", " * ")
        formula = formula.replace("/", " / ")
        formula = formula.replace("^", " ** ")
        formula = formula.replace("(", " ( ")
        formula = formula.replace(")", " ) ")
        formula = formula.replace("a", str(a))
        formula = formula.replace("b", str(b))
        formula = formula.replace("c", str(c))
        formula = formula.replace("d", str(d))
        formula = formula.replace("p", "3.1415926")
        _list_formula = formula.split(" ")
        _formula2 = ""
        for oper in _list_formula:
            try:
                _float_oper= str(float(oper))
                _formula2 = _formula2 + _float_oper
            except ValueError:
                _formula2 = _formula2 + oper
        _g ={"__builtins__":{}}
        try:
            return eval(_formula2, _g)
        except:
            raise ValueError( _("Invalid formula") )


class Decimals(object):
    """base.Decimals:
    
    Description:
        Decimals object
    Constructor:
        base.Decimals(DN=2, DD=2, DSP=2, DS=2,
                      DFC=3, DFPU=3, DFUO=3, DFA=3,
                      DRP=3, DRC=3, DRUO=3, DRA=3,
                      DP=2, DC=2, DPU=2, DUO=2, DEA=2, DES=2,
                      DIR=2, DIM=2, DIRC=2, DIMC=2, DCD=2,
                      DIVISA="EUR")
    Ancestry:
    +-- object
      +-- Decimals
    Atributes:
        "DN": Number of decimal places of the field "equal-size parts" in the
            measure lines.
            Default: 2 decimal places.
        "DD": Number of decimal places of the three dimensions in the 
            measure lines.
            Default: 2 decimal places.
        "DSP": Number of decimal places of the subtotal of a measure.
            Default: 2 decimal places.
        "DS": Number of decimal places of the total sum of a measure.
            Default: 2 decimal places.
        "DFC": Number of decimal places of the yield factor in a decomposition
            of a chapter or subchapter.
            Dafault: 3 decimal places.
        "DFPU": Number of decimal places of the yield factor in a decomposition
            of a unitary budget.
            Default: 3 decimal places.
        "DFUO": Number of decimal places of the yield factor in a decomposition
            of a unit of work.
            Default: 3 decimal places.
        "DFA": Number of decimal places of the yield factor in a decomposition
            of a Auxiliary element.
            Default: 3 decimal places.
        "DRC": Number of decimal places of the yield in a 
            decomposition of a chapter or subchapter.
            Number of decimal places of the result of the multiplictaion of
            the yield (or measure) and the factor in a decomposition of a 
            chapter or subcharter.
            Default: 3 decimal places.
        "DRPU": Number of decimal places of the yield in a decomposition
            of a unitary budget record.
            Number of decumal places of the result of the multiplication of 
            the factor and the yield in a decompositon of a untitary budget.
            Default: 3 decimal places.
        "DRUO": Number of decimal places of the yield in a decomposition of a
            unit of work.
            Decimal places of the result of the multiplication of the yield
            and the factor in a descomposition of a unit of work.
            Default: 3 decimal places.
        "DRA": Number of decimal places of the yield in a decompositon of a
            auxiliar element.
            Number of decimal places of the result of the multiplication of 
            the yield and the factor in a descomposition of a auxilar element.
            Default: 3 decimal places.
        "DP": Number of decimal places of the price of a budget.
            Default: 2 decimal places.
        "DC": Number of decimal places of the price of a chapter or subchapter.
            Default: 2 decimal places.
        "DPU": Number of decimal places of the price of a unitary budget.
            Default: 2 decimal places.
        "DUO": Number of decimal places of the price of a unit of work.
            Default: 2 decimal places.
        "DEA": Number of decimal places of the price of a auxiliar element.
            Default: 2 decimal places.
        "DES": Number of decimal places of the price of the simple elements.
            Default: 2 decimal places.
        "DIR": Number of decimal places of the resulting amount to multiply 
            the total yield and the price of the elements of a unit of work or
            a auxiliar element.
        "DIRC": Number of decimal places of the resulting amount to multiply 
            the total yield and the price of the elements of a budget, chapter
            or a subchapter.
        "DCD": Number of decimal places ot the resulting amount to sum the
            direct costs of a unit of work (and auxiliar element).
            Number of decimal places of the indirect costs.
            Default: 2 decimal places.
        "DIVISA": monetary unit.
    Methods:
        haskey(key)
        getD(recordtype)
        getDF(recordType)
        getDR(recordType)
        getDI(recordType)
    """
    # TODO: get/set methods

    def __init__(self,
                 DN=2, DD=2, DSP=2, DS=2,
                 DFC=3, DFPU=3, DFUO=3, DFA=3, 
                 DRC=3, DRPU=3, DRUO=3, DRA=3, 
                 DP=2, DC=2, DPU=2, DUO=2, DEA=2, DES=2, 
                 DIR=2, DIRC=2, DCD=2, 
                 DIVISA="EUR" ):
        self.DN = DN
        self.DD = DD
        self.DSP = DSP
        self.DS = DS
        self.DFP = 3
        self.DFC = DFC
        self.DFPU = DFPU
        self.DFUO = DFUO
        self.DFA = DFA
        self.DRP = 3
        self.DRC = DRC
        self.DRPU = DRPU
        self.DRUO = DRUO
        self.DRA = DRA
        self.DP = DP
        self.DC = DC
        self.DPU = DPU
        self.DUO = DUO
        self.DEA = DEA
        self.DES = DES
        self.DIR = DIR
        self.DIRC = DIRC
        self.DCD = DCD
        self.DIVISA = DIVISA

    def __getitem__(self, key):
        return self.__dict__[key]

    def haskey(self, key):
        return key in self.__dict__

    def getD(self, recordType):
        # DP: budget.
        # DC: chapter and subcharter. 
        # DUO: unit.
        # DEA: auxiliar element.
        # DES: simple element.
        _hierarchy = recordType.hierarchy
        if _hierarchy == 0: #budget, type 0, subtipe "OB"
            _decimal = self.DP
        elif _hierarchy == 1: #chapter/subcharter, type 0, subtipe ""
            _decimal = self.DC
        else: # other
            _type = recordType.type
            _subtype = recordType.subtype
            if _subtype == "EA": # auxiliar element type 0 subitype "EA"
                _decimal = self.DEA
            if _subtype == "PU": # unitary budget type 0 subitype "PU"
                _decimal = self.DPU
            elif (_type in [1, 2, 3] or
                  _subtype in ["H", "Q", "%", "MC", "MCr", "MM", "MS", "ME",
                                "MCu", "Mal","ML", "M"]
                 ): # simple element
                _decimal = self.DES
            else: # unit  type 0, subtipe ["EU", "EC", "EF", "PA"]
                _decimal = self.DUO
        return _decimal

    def getDF(self, recordType):
        # Factor: DF
        #   ->DFP: Budget
        #   ->DFC: Chapter/Subchapter
        #   ->DFUO: Unit
        #   ->DFA: Auxiliar
        #   ->DFPU: Unitary budget
        if recordType.hierarchy == 0: #budget
            _decimal = self.DFP
        elif recordType.hierarchy == 1: #chapter/subcharter
            _decimal = self.DFC
        else: # other
            if recordType.subtype == "EA": # auxiliar element
                _decimal = self.DFA
            if recordType.subtype == "PU": # unitary budget element
                _decimal = self.DFPU
            else: # unit  EU EC EF PA
                _decimal = self.DFUO
        return _decimal

    def getDR(self, recordType):
        # Yield: DR
        #   ->DRP: Budget
        #   ->DRC: Chapter/Subchapter
        #   ->DRUO: Unit
        #   ->DRA: Auxiliar
        #   ->DRPU: Unitary budget
        if recordType.hierarchy == 0: #budget
            _decimal = self.DRP
        elif recordType.hierarchy == 1: #chapter/subcharter
            _decimal = self.DRC
        else: # other
            if recordType.subtype == "EA": # auxiliar element
                _decimal = self.DRA
            if recordType.subtype == "PU": # unitary budget element
                _decimal = self.DRPU
            else: # unit
                _decimal = self.DRUO
        return _decimal

    def getDI(self, recordType):
        # DIRC: budget, chapter and subcharter. 
        # DIR: unit, auxiliar element.
        _hierarchy = recordType.hierarchy
        _subtype = recordType.subtype
        if _hierarchy == 0 or _hierarchy == 1 or _subtype == "PU":
            #budget, type 0, subtipe "OB"
            #chapter/subcharter, type 0, subtipe ""
            #unitary budget, type 2, subtype "PU"
            _decimal = self.DIRC
        else: # other
            # auxiliar element type 0 subitype "EA"
            # unit  type 0, subtipe ["EU", "EC", "EF", "PA", "PU"]
            _decimal = self.DIR
        return _decimal


class Sheet(object):
    """base.Sheet:
    Description:
        Sheet of conditions object
    Constructor:
        base.Sheet(sheet_dict)
    Ancestry:
    +-- object
      +-- Sheet
    Atributes:
        "sheet_dict": { <Field key> : { <Section key> : <Paragraph key>}
            <Field key>: must be in Budget.SheetFields
            <Section key>: must be in Budget.SheetSections
            <Paragraph key>: must be in Budget.SheetParagraph
    Methods:
        {get/set}Sheet_dict
        getFields
        getSections
        getParagraph
        addField
        addSection
    """
    __slots__ = ["_Sheet__sheet_dict"]

    def __getstate__ (self):
        return (self.__sheet_dict,)

    def __setstate__(self,tuple):
        self.__sheet_dict = tuple[0]

    def __init__(self):
        self.__sheet_dict = {}

    def getSheet_dict(self):
        return self.__sheet_dict

    def setSheet_dict(self, sheet_dict):
        if not isinstance(sheet_dict, dict):
            raise ValueError( _("sheet_dict must be a dictionay") )
        self.__sheet_dict = sheet_dict

    def getFields(self):
        return self.sheet_dict.keys()

    def getSections(self, field):
        if field in self.__sheet_dict:
            return self.__sheet_dict[field].keys()
        else:
            return None

    def getParagraph(self, field, section):
        if (field in self.__sheet_dict and
            section in self.__sheet_dict[field]):
            return self.__sheet_dict[field][section]
        else:
            return None

    def addField(self, field, section_dict):
        if not isinstance(field, str):
            raise ValueError( _("sheet field must be a string") )
        if not isinstance(section_dict, dict):
            raise ValueError( _("section_dict must be a dictionary") )
        self.__sheet_dict[field] = section_dict

    def addSection(self, field, section, paragraph):
        if not isinstance(field, str):
            raise ValueError( _("sheet field must be a string") )
        if not isinstance(section, str):
            raise ValueError( _("sheet section must be a string") )
        if not isinstance(paragraph, str):
            raise ValueError( _("sheet paragraph must be a string") )
        if not field in self.__sheet_dict:
            self.addField(field, { })
        _field = self.__sheet_dict[field]
        _field[section] = paragraph

    sheet_dict = property(getSheet_dict, setSheet_dict, None,
    """Sheet dictionary { <Field key> : { <Section key> : <Paragraph key>}""")


class Budget(object):
    """base.Budget:
    
    Description:
        Budget object
        +-- __records: dictionary records { code : Record }
        +-- __synonyms: synonyms dictionary. TODO { "code" : ["synonym",],}
            Each record code can have synonym codes.
        +-- __root: the root record code
        +-- __decimals: List with the decimal places used to round the 
-            result of the calculations with prices and measures
-            The values are Decimals objects 
-            The <0> objets is the default Decimals (seted in FIEBDC-3),
-            The others keys are for the diferent groups of Prices
        +-- __percentages: percentages dictionary:
                      { "CI"  : "",
                        "GG"  : "",
                        "BI"  : "",
                        "BAJA": "",
                        "IVA" : ""}
        +-- __file_owner
        +-- __title_list: titles list: [ "Header", ["Title1", "Title2", ... ] ]
            List with the Headers and list of Titles for prices and
            decimal places.
            The Headers is the type of hierarchy of the prices
            Each Title have a group of Prices and a Decimals definition
            The records can have diferent prices for diferent ages, geografical
            places, ...
        +-- __title_index: A integer. The active group of Prices and Decimals.
        +-- __sheet_sections: sheet sections dictionary
                   { sheet_code : sheet_title }
        +-- __sheet_fields: sheet fields dictionary { field_code : field_title }
        +-- __sheet_paragraphs: sheet paragraphs dictionary 
                           { paragraph_code : paragraph_text}
        +-- __companys: Dictionary whith companys object
                   { company_code: company_object }
        
        +-- __comment
-       +-- __date
-       +-- __budgetType" A integer. Type of data in budget
-                0 -> Undefined
-                1 -> Base data.
-                2 -> Budget.
-                3 -> Certificate.
-                4 -> Base date update.
-       +-- __budgetCerficateOrder" Only valid if budgetType is 3.
-       +-- __budgetCerficateDate" Only valid if budgetType is 3
-       +-- __tec_info": Dictionary whith tecnical information
-                        {ti_code : ["desciption text", "unit"]}
-       +-- __labels": Label dictionary { "label": [ "code", ], }
        
        
    Constructor:
        base.Budget()
    Ancestry:
    +-- object
      +-- Budget
    Atributes:
        No public Atributes
    Methods:
        iter
        iterPreOrder
        iterPostOrder
        getRoot(self)
        hasPath(self, path)
        getchildren(self, code)
        setOwner(self, owner)
        setDate(self, date)
        setComment(self, comment)
        setBudgetType(self, type)
        setCertificateOrder(self, order)
        setCertificateDate(self, date)
        setTitleList(self, title)
        getTitleList(self)
        getActiveTitle(self)
        setDecimals(self, dictionary)
        getDecimals(self, decimal="All", N=None)
        setPercentages(self, dictionary)
        getPercentages(self, percentage="All")
        getAllParents(self, code)
        getAllchildren(self, code)
        getNDecomposition(self, code, N)
        getDecomposition(self,code)
        getMeasure(self, path)
        getStrYield
        getStrFactor
        setTree(sef, code, child_code, position, factor, yield_, total,
                list_lines, label, type)
        eval_formula(self, formula, a, b, c, d)
        getText(self, code)
        setText(self, code, text)
        setRecord(self, code, synonyms, hierarchy, unit, sumary, ...
        hasRecord(self, code)
        getRecord
        addPriceToRecord
        getStrPriceFromRecord
        getCode(self, path)
        getAmount
        getStrAmount
        setSheetSection(self, sheet_code, sheet_title)
        hasSheetSection(self, section)
        setSheetSections(self,dictionary)
        setSheetField(self, field_code, field_title)
        hasSheetField(self, field)
        getSheetField(self, field)
        setSheetFields(self, field_dict)
        setSheetParagraph(self, paragraph_code, paragraph_text)
        hasSheetParagraph(self, paragraph)
        getSheetParagraph(self, paragraph)
        setSheetParagraphs(self, paragraph_dict)
        setSheetRecord(self, record_code,field, section_dict)
        addFile(self, record_code, filename)
        setCompany(self, code, summary, name, offices, cif, web, email )
        getCompany
        getCompanyKeys
        addTecInfo(self, ti_code, text, unit)
        hasTecInfo(self, ti_code)
        getTecInfo(self, ti_code)
        setTecnicalInformation(self, _record_code, _ti_dict)
        changeCode(self, record_code, new_rocord_code)
        addLabel
        setParametricSelectComment
        setParametricSummary
        setParametricText
    """

    def __init__(self):
        """__init__(self)
        
        Initialize the budget atributes
        """
        # title_index: A integer. The active group of Prices and Decimals.
        # TODO: change title_index
        self.__title_index = 0
        # List with the decimal places used to round the 
        # result of the calculations with prices and measures
        # The values are Decimals objects 
        # The <0> objets is the default Decimals (seted in FIEBDC-3),
        # The others keys are for the diferent groups of Prices
        self.__decimals = [Decimals(), Decimals()]
        # Dictionary with the percentages
        #    keys:
        #        "CI"    Indirect Cost
        #        "GG"    General expenses
        #        "BI"    Industrial benefit
        #        "BAJA"  Low (what this do here?)
        #        "IVA"   Tax
        self.__percentages = { "CI" : "" ,"GG": "", "BI": "",
                               "BAJA": "", "IVA" : ""}
        # List with the Headers and list of Titles for prices and
        #    decimal places.
        #    [ "Header", ["Title1", "Title2", ... ] ]
        #    The records can have diferent prices for diferent ages, geografical
        #    places, ...
        #    The Headers is the type of hierarchy of the prices
        #    Each Title have a group of Prices and a Decimals definition
        self.__title_list = [ "", [ ] ]
        # The root record code.
        self.__root = None
        self.__file_owner = ""
        self.__comment = ""
        self.__budgetCerficateOrder = None  # Only valid if budgetType is 3.
        self.__budgetCerficateDate = None   # Only valid if budgetType is 3.
        self.__date = (0,0,0)
        # budgetType: A integer. Type of data in budget
        #        0 -> Undefined
        #        1 -> Base data.
        #        2 -> Budget.
        #        3 -> Certificate.
        #        4 -> Base date update.
        self.__budgetType = 0
        # Dictionary with the budget records: { "code" : Record object, }
        self.__records = { }
        # Dictionary with the records synonyms.
        #    { "code" : ["synonym",],}
        #    Each record code can have synonym codes.
        self.__synonyms = { }
        # sheet_sections: Dictionary whith de sheet sections
        self.__sheet_sections = { }
        # sheet_fields: Dictionary whith sheet fields
        self.__sheet_fields = { }
        # sheet_paragraphs: Dictionary whith sheet paragraphs
        self.__sheet_paragraphs = { }
        # companys: Dictionary whith companys object
        #           { company_code: company_object }
        self.__companys = { }
        # tec_info: Dictionary whith tecnical information
        #           {ti_code : ["desciption text", "unit"]}
        self.__tec_info = { }
        # labels: Label dictionary { "label": [ "code", ], }
        self.__labels = { }

    def __getstate__(self):
        return (self.__title_index, self.__decimals, self.__percentages,
                self.__title_list, self.__root, self.__file_owner,
                self.__records, self.__synonyms, self.__sheet_sections,
                self.__sheet_fields, self.__sheet_paragraphs,self.__companys,
                self.__tec_info, self.__labels)

    def __setstate__(self, tuple):
        self.__title_index = tuple[0]
        self.__decimals = tuple[1]
        self.__percentages = tuple[3]
        self.__title_list = tuple[4]
        self.__root = tuple[4]
        self.__file_owner = tuple[5]
        self.__records = tuple[6]
        self.__synonyms = tuple[7]
        self.__sheet_sections = tuple[8]
        self.__sheet_fields = tuple[9]
        self.__sheet_paragraphs = tuple[10]
        self.__companys = tuple[11]
        self.__tec_info = tuple[12]
        self.__labels = tuple[13]

    def iter(self):
        for record in self.__records:
            yield record

    def iterPreOrder(self, recordCode, codes=None):
        if codes is None:
            codes = []
        _children = self.getchildren(recordCode)
        for _child in _children:
            if not _child in codes:
                codes.append(_child)
                self.iterPreOrder(_child, codes)
        return codes

    def iterPostOrder(self, recordCode, codes=None):
        if codes is None:
            codes = []
        _children = self.getchildren(recordCode)
        for _child in _children:
            if not _child in codes:
                self.iterPreOrder(_child, codes)
                codes.append(_child)
        return codes

    def getRoot(self):
        """getRoot(self)
        
        Returns the root record code
        """
        return self.__root

    def hasPath(self, path):
        """hasPath(self, path)
        
        path: The path of the record in the budget, It is a tuple.
        Tests if the path is valid in the budget
        """
        try:
            self.getCode(path)
            return True
        except ValueError:
            return False

    def getchildren(self, code):
        """getchildren(self, code)
        
        code: a record code.
        Return a list whith the child codes of a record
        """
        _record = self.__records[code]
        _children = _record.children
        _child_code = [ _child.code for _child in _children ]
        return _child_code

    def setOwner(self, owner):
        """setOwner(self, owner)
        
        owner: data owner 
        Set the data owner.
        """
        if isinstance(owner, basestring):
            self.__file_owner = owner
        else:
            raise  TypeError( _("Owner must be a string") )

    def setDate(self, date):
        """setOwner(self, date)
        
        date (_y, _m, _d)
        Set the date when de file was generated
        """
        if isinstance(date, tuple) and len(date) == 3 and \
           isinstance(date[0], int) and isinstance(date[1], int) and \
           isinstance(date[2], int) and date[1] in range(13) and \
           date[2] in range(32):
            if date[1] != 0 and date[2] != 0:
                datetime.date(*date)
            self.__date = date
        else:
            raise  TypeError(utils.mapping(_("Invalid Date: $1"),(str(date),)))

    def setComment(self, comment):
        """setOwner(self, comment)
        
        comment: text to comment the budged
        Set the comment.
        """
        if isinstance(comment, basestring):
            self.__comment = comment
        else:
            raise  TypeError( _("Comment must be a string") )

    def setBudgeType(self, budget_type):
        """setOwner(self, budget_type)
        
        budget_type: type of data in budget
            0 -> Undefined
            1 -> Base data.
            2 -> Budget.
            3 -> Budget certificate.
            4 -> Base date update.
        Set the budget type.
        """
        if budget_type in [1, 2, 3, 4]:
            self.__budgetType = budget_type
        else:
            raise  ValueError( _("Budget type must be 1, 2, 3 or 4.") )

    def setCertificateOrder(self, certificate_order, certificate_date):
        """setOwner(self, budget_type)
        
        certificate_order: certificate number
        certificate_date: certificate date
        Set the certificate order and date.
        """
        if isinstance(certificate_order, int):
            self.__budgetCerficateOrder = certificate_order
        else:
            raise  ValueError( _("Certificate order must be a integer.") )

    def setCertificateDater(self, certificate_date):
        """setCertidicateDate(self, certificate_date)
        
        Set the certificate date.
        """
        if isinstance(certificate_date, tuple) and \
           len(certificate_date) == 3 and \
           isinstance(certificate_date[0], int) and \
           isinstance(certificate_date[1], int) and \
           isinstance(certificate_date[2], int):
            datetime.date(*certificate_date)
            self.__budgetCerficateDate = certificate_date
        else:
            _str = _("Budget certificate Date must be a valid Date.")
            raise  ValueError(_str)

    def setTitleList(self, title_list):
        """setTitleList(self, title_list)
        
        title_list: [ "Header", ["Title1", "Title2", ... ] ]
        Set the header and titles for the price groups and decimals.
        """
        title_list[0] = str(title_list[0])
        if isinstance(title_list, list) and isinstance(title_list[1], list):
            for i in range(len(title_list[1])):
                title_list[1][i] = str(title_list[1][i])
            self.__title_list = title_list
        else:
            raise TypeError( _("Invalid title list format") )

    def getTitleList(self):
        """ getTitleList(self)
        
        Returns the header and titles for the price groups and decimals.
        """
        return self.__title_list

    def getActiveTitle(self):
        """getActiveTitle(self)
        
        Returns the active Title of price group
        """
        return self.__title_index

    def setDecimals(self, dictionary, N):
        """setDecimals(self, dictionary, N)
        
        dictionay: the decimal dictionary
        N: the price group
        Sets the Decimals for a price group.
        """
        if N == -1 or N == len(self.__decimals):
            _default_decimals = self.__decimals[0]
            self.__decimals.append(_default_decimals)
        elif N < len(self.__decimals):
            _default_decimals = self.__decimals[N]
        else:
            raise IndexError( _("Invalid Index Title") )
        for _decimal in dictionary:
            if dictionary[_decimal] == "":
                dictionary[_decimal] = eval("_default_decimals." + _decimal)
        decimals = Decimals(dictionary["DN"], dictionary["DD"],
                            dictionary["DSP"], dictionary["DS"],
                            dictionary["DFC"],
                            dictionary["DFPU"], dictionary["DFUO"],
                            dictionary["DFA"], dictionary["DRC"],
                            dictionary["DRPU"], dictionary["DRUO"],
                            dictionary["DRA"], dictionary["DP"],
                            dictionary["DC"], dictionary["DPU"],
                            dictionary["DUO"], dictionary["DEA"],
                            dictionary["DES"], dictionary["DIR"],
                            dictionary["DIRC"], dictionary["DCD"],
                            dictionary["DIVISA"])
        self.__decimals[N] = decimals

    def getDecimals(self, decimal=None, N=None):
        """getDecimals(self,decimal="All",N=None)
        
        decimal:
            "All": Return a Decimals objet for a price group
            "keys": Return the keys of a Decimal object
            key: Return a Decimal value for a price group
        N: the price group None,1,2,..
            None: Return the active price group
        """
        if decimal is None: decimal = "All"
        if N is None: N = self.getActiveTitle()
        if decimal == "All":
            return self.__decimals[N+1]
        elif decimal == "keys":
            return self.__decimals[N+1].keys
        elif self.__decimals[N+1].haskey(decimal):
            return self.__decimals[N+1][decimal]
        else:
            raise KeyError( _("Decimal Key error") )

    def setPercentages(self, dictionary):
        """setPercentages(self, dictionary):
        
        dictionary: the percentage dictionary
        Sets the percentage dictionary.
        """
        _default_percentages = self.__percentages
        for percentage in dictionary:
            if dictionary[percentage] == 0: 
                dictionary[percentage] = ""
            elif dictionary[percentage] == "":
                dictionary[percentage] = _default_percentages[percentage]
        _percentages = { "CI": dictionary["CI"],
                         "GG": dictionary["GG"],
                         "BI": dictionary["BI"],
                         "BAJA": dictionary["BAJA"],
                         "IVA" : dictionary["IVA"]}
        self.__percentages = _percentages

    def getPercentages(self, key=None):
        """getPercentages(self, key=None)
        
        key:
            "All": Return the Percentages dictionary
            "keys": Return the keys of a Percentages object
            key: Return a Percentages value for the key
        """
        if Key is None:
            key = "All"
        if key == "All":
            return self.__percentages
        elif key == "keys":
            return self.__percentages.keys
        elif key in self.__percentages:
            return self.__percentages[key]
        else:
            raise KeyError( _("Invalid Percentage key") )

    def getAllParents(self,code):
        """getAllParents(self,code)
        
        code: a record code.
        Returns a list with all the parents of a record
        All record which the record is in its descomposition list, 
        including the parents of the parents
        """
        if code in self.__records:
            _parents = self.__records[code].parents
            if len(_parents) == 0: return [ ]
            for _antecesor in _parents[:]:
                _parents = _parents + self.getAllParents(_antecesor)
            return _parents
        else:
            return [ ]

    def getAllchildren(self,code):
        """getAllchildren(self,code
        
        code: a record code.
        Returns a list with all the children of a record, including
        the children of the children
        """
        if code in self.__records:
            _children = self.__records[code].children
            _children = [ _child.code for _child in _children ]
            for _child in _children[:]:
                _children = _children + self.getAllchildren(_child)
            return _children
        else:
            return [ ]

    def getNDecomposition(self, code, N):
        """getDecomposition(self,path)
        
        path: the path for a record
        Returns the Decomposition object of a record
        """
        _record = self.getRecord(code)
        _decomposition_list = _record.children
        _decomposition = _decomposition_list[N]
        return _decomposition
    
    def getDecomposition(self, path):
        """getDecomposition(self,path)
        
        path: the path for a record
        Returns the Decomposition object of a record
        """
        if path == (0,):
            _type = self.getRecord(self.__root).recordType
            return Decomposition( 0, self.__root,
                   [Measure(self.getDecimals(), _type,
                            0.0, [], "", 1.0, 1.0)])
        else:
            return self.getNDecomposition(self.getCode(path[:-1]), path[-1])

    def getMeasure(self, path):
        """getMeasure(self, path)
        
        path: the path for a record
        Return the measute object of a record
        """
        _decomposition = self.getDecomposition(path)
        _measure = _decomposition.budgetMeasures[0]
        return _measure

    def getStrYield(self, measure, recordType):
        #_DR = measure.getDR(self.getDecimals())
        _DR = abs(self.getDecimals().getDR(recordType))
        _yield = ("%." + str(_DR) + "f" ) % measure.yield_
        return _yield

    def getStrFactor(self, measure, recorType):
        _DF = abs(self.getDecimals().getDF(recordType))
        #_DF = measure.getDF(self.getDecimals())
        _factor = ("%." + str(_DF) + "f" ) % measure.factor
        return _factor

    def setTree(self, code, child_code, position, factor, yield_, total,
                list_lines, label, type_):
        """setTree(self, code, child_code, position, factor,yield_, total,
        list_lines, label, type_)
        
        code: the parent record code
        child_code: child record code
        position: position of child record in record parent record
            decomposition. Position == -1 -> new child
        factor:
        yield_:
        total: total measure (float)
        list_lines: list of measure lines
            [ [linetype, comment, units, length, width, height], ... ]
            linetype:
                empty string -> Normal
                1 -> Parcial Subtotal
                2 -> Accumulated Subtotal
                3 -> Formula, the comment is a formula.
            comment: Can be a descriptive text or a formula
                Valid Operator: '(', ')', '+', '-', '*', '/' and '^'
                Valid variable: 'a', 'b', 'c','d'y 'p' (Pi=3.1415926)
            units: Number of Units (a)
            length: Length (b)
            width: Width (c)
            height: Height (d)
        label: Record Identifiers that are used by some measure programs.
        type_: type of action
            M: Set measure
            A: Add measure
        Sets the decomposition of a record in a child record
        """
        if code is None: # No-estructured measures
            code = self.getRoot()
            if code == None: # No root
                print( "No-estructured measures. Adding root record " +  
                str(self.setRecord("root", [], 0, "", "", [0.0,], [(1,1,1970)],
                                     0, "") ).encode("utf8"))
                code = self.getRoot()

        if not utils.is_valid_code(code)[0]:
            raise ValueError( utils.mapping(_("Invalid parent code: $1"),
                                            (str(code),)) )
        if not utils.is_valid_code(child_code)[0]:
            raise ValueError( utils.mapping(_("Invalid child code: $1 $2"),
                                           (str(code),str(child_code))) )
        if not isinstance(position, int):
            raise ValueError( utils.mapping(_("Invalid position in measure "\
                  "$1, in code $2"), (str(parent_code), str(position))) )
        # Test circular references
        _all_parent_list = self.getAllParents(code) + [ code ]
        _all_child_list = self.getAllchildren(child_code) + [ child_code ]
        for _parent_code in _all_parent_list:
            if _parent_code in _all_child_list:
                # TODO: change return to except
                _str = _("Circular Decomposition, parent code: "\
                         "$1, child code: $2, repeated code: $3")
                print(utils.mapping(_str, (str(code), str(child_code),
                                           str(_parent_code))).encode("utf8") )
                return

        # Creating reference to parent code in child record
        if child_code in self.__records:
            _child_record = self.__records[child_code]
        else:
            _child_record = self.setRecord(child_code, [], -1, "", "", [], [],
                                     "", "")
        if code in self.__records:
            code = self.__records[code].code
        _child_record.appendParent(code)
        child_code = self.__records[child_code].code
        if code in self.__records:
            # if the code exits retake previous values.
            _record = self.__records[code]
            _child_number = len(_record.children)
            if position == -1: # New child
                position = _child_number
            # No-estructured measures or empty position (error in FIEBDC file)
            if position == -2: 
                positions = _record.getChildPositions(child_code)
                if len(positions) == 1:
                    position = positions[0]
                    _str = _("No-estructured measure or empty position. " \
                             "Parent Code: $1, Child code: $2, Position: $3")
                    print(utils.mapping(_str,(str(code), str(child_code),
                                        str(position))).encode("utf8") )
                else:
                    position = _child_number
                    _str = _("No-estructured measure or empty position. "\
                             "Repeated child in unspecified position. "\
                             "It is impossible to determine the position. "\
                             "New child is added in the decomposition. "\
                             "Parent code: $1, Child code: $2, Position: $3")
                    print(utils.mapping(_str,(str(code), str(child_code),
                                        str(position))).encode("utf8") )
            if position == _child_number:
                # The record do not have the child
                if not isinstance(factor, float): factor = 1.0
                if not isinstance(yield_, float): yield_ = 1.0
                if not isinstance(total, float): total = 0.0
                if not isinstance(list_lines, list): list_lines = []
                _child = _record.appendChild(child_code, self.getDecimals(),
                         factor, yield_, total, list_lines, type_, label)
            elif position < _child_number:
                # The record have the child
                _child = _record.children[position]
                if child_code != "" and child_code != _child.code:
                    _child.code = child_code
                if factor != "" :
                    if not isinstance(factor, float):
                        factor == 1.0
                    _child.budgetMeasures[0].setFactor(factor,
                        self.getDecimals(), _record.recordType)
                if yield_ != "":
                    if not isinstance(yield_, float):
                        yield_ = 1.0
                    _child.budgetMeasures[0].setYield(yield_, 
                        self.getDecimals(), _record.recordType)
                _measure = _child.budgetMeasures[0]
                if total != "":
                    if not isinstance(total, float):
                        yield_ = 0.0
                    _measure.setMeasure(total, self.getDecimals())
                if isinstance(list_lines, list) and len(list_lines) > 0:
                    _measure.buildMeasure(list_lines, type_, self.getDecimals(),
                                          _record.recordType)
                if isinstance(label, str) and label != "" :
                    _measure.label = label
            else:
                # TODO: change return for except
                _str = _("Error: Invalid child position in "
                         "decomposition. Parent code: $1 Child code: $2 "\
                         "Position: $3")
                print(utils.mapping(_str, (str(code), str(child_code),
                                    str(position))).encode("utf8") )
                return
        else:
            if child_code == "" :
                _str = _("Error: Empty child code. Parent code: "\
                         "$1 Position: $2")
                print(utils.mapping(_str, (str(code),
                                           str(position))).encode("utf8") )
                return
            if position == -1:
                position = 0
            elif position != 0:
                _str = _("Error: Invalid child position in "\
                         "decomposition. Parent code: $1 Child code: $2 "\
                         "Position: $3")
                print(utils.mapping(_str, (str(code), str(child_code),
                                    str(position))).encode("utf8") )
                return
            if not isinstance(factor, float):
                factor = 1.0
            if not isinstance(yield_, float):
                yield_ = 1.0
            if not isinstance(total, float):
                total = 1.0
            _record = self.setRecord(code, [], "", "", "", [], [],
                                     "", "")
            _child = _record.appendChild(child_code, self.getDecimals(),
                         factor, yield_, total, list_lines, type_, label)

    def eval_formula(self, formula, a, b, c, d):
        """eval_formula(self, formula, a, b, c, d)
        
        formula:
            Valid Operator: '(', ')', '+', '-', '*', '/' and '^'
            Valid variable: 'a', 'b', 'c','d'y 'p' (Pi=3.1415926)
        units: Number of Units (a)
        length: Length (b)
        width: Width (c)
        height: Height (d)

        Evals the formula and return the result
        """
        if a == "": a = 0.0
        if b == "": b = 0.0
        if c == "": c = 0.0
        if d == "": d = 0.0
        try:
            a = float(a)
        except:
            raise ValueError( _("'a' value must be a float number") )
        try:
            b = float(b)
        except:
            raise ValueError( _("'b' value must be a float number") )
        try:
            c = float(c)
        except:
            raise ValueError( _("'c' value must be a float number") )
        try:
            d = float(d)
        except:
            raise ValueError( _("'d' value must be a float number") )
        # spaces are erased
        sre.sub("[ ]","",formula)
        # operators and varibles are replaced
        formula = formula.replace("+", " + ")
        formula = formula.replace("-", " - ")
        formula = formula.replace("*", " * ")
        formula = formula.replace("/", " / ")
        formula = formula.replace("^", " ** ")
        formula = formula.replace("(", " ( ")
        formula = formula.replace(")", " ) ")
        formula = formula.replace("a", str(a))
        formula = formula.replace("b", str(b))
        formula = formula.replace("c", str(c))
        formula = formula.replace("d", str(d))
        formula = formula.replace("p", "3.1415926")
        _list_formula = formula.split(" ")
        _formula2 = ""
        for oper in _list_formula:
            try:
                _float_oper= str(float(oper))
                _formula2 = _formula2 + _float_oper
            except ValueError:
                _formula2 = _formula2 + oper
        _g = {"__builtins__":{}}
        try:
            return eval(_formula2, _g)
        except:
            raise ValueError( _("Invalid formula") )

    def getText(self,code):
        """getText(self,code)
        
        code: the record code
        Returns the description text of a record
        """
        if code in self.__records:
            return self.__records[code].text
        else:
            raise IndexError( _("Invalid code") )

    def setText(self,code,text):
        """setText(self,code,text)
        
        code: the parent record code
        text: the descripion text
        Sests the description text of a record
        """
        if not utils.is_valid_code(code)[0]:
            _str = _("Invalid record: $1")
            raise ValueError( utils.mapping(_str, (str(code),)) )
        if not code in self.__records:
            _record = self.setRecord(code, [], "", "", "", [], [],
                                     "", "")
            _record.text = text
        else:
            _record = self.__records[code]
            _record.text = text

    def setRecord(self, code, synonyms, hierarchy, unit, summary, price, date,
                  type_, subtype):
        """setRecord(self, code, synonyms, hierarchy, unit, summary, price,
                     date, type_, subtype)
        
        code: Code string
        synonyms: List of synonym codes of the record
        hierarchy:
            0 -> root
            1 -> Chapter/Subchapter
            2 -> Other
        unit: unit of measure record
        summary: Short description of a record
        price: List of prices
        date: List of dates
        "type_" and "subtype":
            0 Without classifying
               EA  Auxiliary element
               EU  Unitary element
               EC  Complex element
               EF  Functional element
               OB  Construction site
               PA  Cost overrun
               PU  Unitary budget
            1 Labourforce 
               H   Labourforce
            2 Machinery and auxiliary equipment
               Q   Machinery
               %   Auxiliary equipment
            3 Building materials
               MC  Cement
               MCr Ceramic
               MM  Wood
               MS  Iron and steel
               ME  Energy
               MCu Copper
               MAl Aluminium
               ML  Bonding agents
               M   Others materials
          Hierarchy            type subtype
            0->root         -> 0 -> None,OB
            1->[sub]chapter -> 0 -> None,PU
            2->Other        -> 0 -> None,EA,EU,EC,EF,PA
                               1 -> None,H
                               2 -> None,Q,%
                               3 -> None,MC,MCr,MM,MS,ME,MCu,Mal,ML,M
        Adds a record in the budget
        """
        # hierarchy
        if hierarchy == 0 :
            # is the root record
            if self.__root is None:
                self.__root = code
            else:
                print(_("Only can be one root record").encode("utf8") )
                return
                # TODO: If the root is created in settree.
                #       No-estructured measures
                # TODO  Rewrite root values
        # retake previous values.
        # TODO: test synonyms
        _budget = self
        if not code in self.__records:
            if code[-1] == "$":
                _record = ParametricRecord(_budget.getDecimals(), code,
                                           synonyms, hierarchy,
                                           unit, summary, [], type_, subtype,
                                           [], "")
            else:
                _record = Record(_budget.getDecimals(), code, synonyms,
                                 hierarchy, unit,
                                 summary, [], type_, subtype,[], "")
            self.__records[code] = _record
            _prices = [[price[i], date[i]] for i in range(len(price))]
            _record.setPrices(_prices, self.getDecimals())
        else:
            _record = self.__records[code]
            code = _record.code
            if len(synonyms) != 0 and synonyms[0] == "":
                synonyms = _record.synonyms
            if unit == "":
                unit = _record.unit
            if summary == "":
                summary = _record.summary
            #TODO: test empty price list
            if len(price) == 0 or price[0] == "": 
                _prices = _record.prices
            else:
                _prices = [ [price[i], date[i]] for i in range(len(price))]
            if type_ == "":
                type_ = _record.recordType.type
            _record.synonyms = synonyms
            _record.unit = unit
            _record.summary = summary
            _record.setPrices(_prices, self.getDecimals())
            _record.recordType.hierarchy = hierarchy
            _record.recordType.type = type_
            _record.recordType.subtype = subtype
        return _record

    def hasRecord(self,code):
        """hasRecord(self,code)
        
        code: Code record
        Return True if the budget have this record code.
        """
        if code in self.__records:
            return True
        else:
            return False

    def getRecord(self, code):
        """getRecord(self, code)
        
        code: Code record
        Return the Record object
        """
        return self.__records[code]

    def addPriceToRecord(self, price_date, record):
        """addPriceToRecord(self, price, record)
        
        Add a price to the price list of the record.
        price must fulfill:
            - must be a list with two items
            - the first item: price must be a float
        """
        record.addPrice(price_date, self.getDecimals())

    def getStrPriceFromRecord(self, index_price, record, path):
        if record.isPercentage():
            _percentageMasq = record.percentageMasq()
            _parent_code = self.getCode(path[:-1])
            _N_record = path[-1]
            _amount_sum = 0.0
            for N,_code in enumerate(self.getchildren(_parent_code)[:_N_record]):
                _child_record = self.getRecord(_code)
                if _child_record.hasPercentageMasq(_percentageMasq):
                    _path = path[:-1] + (N,)
                    _amount = self.getAmount(_path)
                    _amount_sum = _amount_sum + _amount
            _price = _amount_sum
        else:
            _price = record.getPrice(index_price)
        _D = abs(self.getDecimals().getD(record.recordType))
        _price = ("%." + str(_D) + "f" ) % _price
        return _price

    def getCode(self, path):
        """getCode(self, path)
        
        path: path record in the budget.
        Return the code record
        """
        if isinstance(path, tuple) and len(path)>= 1:
            if path[0] == 0:
                _code = self.__root
                for i in path[1:]:
                    if isinstance(i, int):
                        _record = self.__records[_code]
                        _children_list = _record.children
                        try:
                            _child = _children_list[i]
                        except:
                            raise ValueError( _("This record does not exits") )
                        _code = _child.code
                    else:
                        raise ValueError( _("Path item must be a integer") )
                return _code
            else:
                raise ValueError( _("This record does not exits") )
        else:
            raise ValueError( utils.mapping(_("Path must be a not empty "\
                  "tuple: $1"), (str(path),)) )

    def getAmount(self, path):
        """def getAmount(self,path)
        
        path: record path
        Calculate the record amount
        """
        
        if len(path) == 1:
            # root: amount is the root price
            _root = self.getRecord(self.getRoot())
            _amount = _root.getPrice(self.__title_index)
            return _amount
        else:
            _parent_code = self.getCode(path[:-1])
            _parent_record = self.getRecord(_parent_code)
            _child_number = path[-1]
            
            _decomposition = _parent_record.children[_child_number]
            _factor = _decomposition.budgetMeasures[0].factor
            _yield = _decomposition.budgetMeasures[0].yield_
            _child_code = _decomposition.code
            _child_record = self.getRecord(_child_code)
            _code = self.getCode(path)
            _record = self.getRecord(_code)
            if _record.isPercentage():
                _percentageMasq = _record.percentageMasq()
                _N_record = path[-1]
                _amount_sum = 0.0
                for N,_code in enumerate(self.getchildren(_parent_code)[:_N_record]):
                    _child_record = self.getRecord(_code)
                    if _child_record.hasPercentageMasq(_percentageMasq):
                        _path = path[:-1] + (N,)
                        _amount = self.getAmount(_path)
                        _amount_sum = _amount_sum + _amount
                _price = _amount_sum
            else:
                _price = _child_record.getPrice(self.getActiveTitle())
            
            _DR = abs(self.getDecimals().getDR(_parent_record.recordType))
            _total_yield = round(_factor * _yield, _DR)
            _DI = abs(self.getDecimals().getDI(_parent_record.recordType))
            _amount = round(_total_yield * _price, _DI)
            return _amount

    def getStrAmount(self, path):
        """def getStrAmount(self, path)
        
        path: record path
        Calculate the string record amount
        """
        if len(path) == 1: #root
            _root = self.getRecord(self.getRoot())
            _amount = self.getStrPriceFromRecord(self.__title_index, _root, path)
            return _amount
        else:
            _parent_code = self.getCode(path[:-1])
            _parent_record = self.getRecord(_parent_code)
            _amount = self.getAmount(path)
            _DI = abs(self.getDecimals().getDI(_parent_record.recordType))
            _amount = ("%." + str(_DI) + "f") % _amount
            return _amount

    def setSheetSection(self,sheet_code,sheet_title):
        if not isinstance(sheet_code, str):
            raise ValueError( _("The sheet code must be a string") )
        if not isinstance(sheet_title, str):
            raise ValueError( _("The sheet title must be a string") )
        self.__sheet_sections[sheet_code] = sheet_title

    def hasSheetSection(self, section):
        return section in self.__sheet_sections

    def getSheetSection(self, section):
        return self.__sheet_sections[section]

    def setSheetSections(self,dictionary): 
        if not isinstance(dictionary, dict):
            raise ValueError( _("The sheet sections must be a dictionary") )
        for sheet_code in dictionary.keys():
            self.setSheetSection(sheet_code, dictionary[sheet_code])

    def setSheetField(self, field_code, field_title):
        if not isinstance(field_code, str):
            raise ValueError( _("The field code must be a string") )
        if not isinstance(field_title, str):
            raise ValueError( _("The field title must be a string") )
        self.__sheet_fields[field_code] = field_title

    def hasSheetField(self, field):
        return field in self.__sheet_fields

    def getSheetField(self, field):
        return self.__sheet_fields[field]

    def setSheetFields(self, field_dict):
        if not isinstance(field_dict, dict):
            raise ValueError( _("The sheet field must be a dictionary") )
        for field_code in field_dict.keys():
            self.setSheetField( field_code, field_dict[field_code])

    def setSheetParagraph(self, paragraph_code, paragraph_text):
        if not isinstance(paragraph_code, str):
            raise ValueError( _("The paragraph code must be a string") )
        if not isinstance(paragraph_text, str):
            raise ValueError( _("The paragraph text must be a string") )
        self.__sheet_paragraphs[paragraph_code] = paragraph_text

    def hasSheetParagraph(self, paragraph):
        return paragraph in self.__sheet_paragraphs

    def getSheetParagraph(self, paragraph):
        return self.__sheet_paragraphs[paragraph]

    def setSheetParagraphs(self, paragraph_dict):
        if not isinstance(paragraph_dict, dict):
            raise ValueError( _("The paragraph dict must be a dictionary") )
        for paragraph_code in paragraph_dict.keys():
            self.setSheetParagraph(paragraph_code,
                                   paragraph_dict[paragraph_code])

    def setSheetRecord(self, record_code, field, section_dict):
        if not isinstance(record_code, str):
            raise ValueError( _("The record_code code must be a string") )
        if not isinstance(field, str):
            raise ValueError( _("The field must be a string") )
        if not isinstance(section_dict, dict):
            raise ValueError( _("The section dict must be a dictionary") )
        #-#
        # TODO: Add a empty record?
        if not self.hasRecord(record_code):
            _str = _("Error: The budget do not have this record "\
                   "code and can not be added the sheet text in the field $1. "\
                   "Record Code: $2")
            print(utils.mapping(_str, ( str(field),
                                str(record_code))).encode("utf8") )
            return
        #-#
        if not self.hasSheetField(field):
            self.setSheetField(field, "")
        for section, paragraph in section_dict.iteritems():
            if not self.hasSheetParagraph(paragraph):
                self.setSheetParagraph(paragraph,"")
            if not self.hasSheetSection(section):
                self.setSheetSection(section, "")
            _sheet = self.getRecord(record_code).getSheet()
            _sheet.addSection(field, section, paragraph)

    def addFile(self, record_code, filepath, type_, description):
        if not isinstance(record_code, str):
            raise ValueError( _("The record_code code must be a string") )
        #-# str and unicode
        if not isinstance(filepath, str) and not isinstance(filepath, unicode):
            raise ValueError( _("The filename must be a string") )
        #-#
        # TODO: Add a empty record?
        if not self.hasRecord(record_code):
            print(utils.mapping(_("Error: The budget do not have the record "\
                "code $1 and can not be added the file: $2"),
                (str(record_code), str(filepath))).encode("utf8") )
            return
        #-#
        _record = self.getRecord(record_code)
        _record.addFile(filepath, type_, description)

    def setCompany(self, company_code, sumamary, name, offices,
                   cif, web, email):
        if not isinstance(company_code, str):
            raise ValueError( _("The company code must be a string") )
        if not isinstance(sumamary, str):
            raise ValueError( _("The summary must be a string") )
        if not isinstance(name, str):
            raise ValueError( _("The name must be a string") )
        if not isinstance(offices, list):
            raise ValueError( _("The name must be a list") )
        _offices = []
        for _office in offices:
            if not isinstance(_office, list):
                raise ValueError( _("The office must be a list") )
            if not len(_office) == 10:
                raise ValueError( _("The office must be a 10 items list") )
            for _item in _office[:7] + _office[9:10]:
                if not isinstance(_item, str):
                    raise ValueError( _("This office item must be a "\
                                        "string") )
            for _item in _office[7:8]:
                if not isinstance(_item, list):
                    raise ValueError( _("This office item must be a "\
                                        "list") )
            _offices.append(Office(_office[0],
                                  _office[1],
                                  _office[2],
                                  _office[3],
                                  _office[4],
                                  _office[5],
                                  _office[6],
                                  _office[7],
                                  _office[8],
                                  _office[9]))
        if not isinstance(cif, str):
            raise ValueError( _("The name must be a string") )
        if not isinstance(web, str):
            raise ValueError( _("The web must be a string") )
        if not isinstance(email, str):
            raise ValueError( _("The email must be a string") )
        
        self.__companys[company_code] = Company(company_code, sumamary, name,
                                                _offices, cif, web, email)
    def getCompany(self, company_code):
        return self.__companys[company_code]

    def getCompanyKeys(self):
        return self.__companys.keys()

    def addTecInfo(self, ti_code, text, unit):
        if not isinstance(ti_code, str):
            raise ValueError( _("The tecnical info code must be a string") )
        if not isinstance(text, str):
            raise ValueError( _("The tecnical info description must be a "\
                                "string") )
        if not isinstance(unit, str):
            raise ValueError( _("The tecnical info unit must be a string") )
        self.__tec_info[ti_code] = [text, unit]

    def hasTecInfo(self, ti_code):
        return ti_code in self.__tec_info

    def getTecInfo(self, ti_code):
        return self.__tec_info[ti_code]

    def setTecnicalInformation(self, record_code, ti_dict):
        """setTecnicalInformation(record_code, ti_dict)
        
        Sets the tecnical information to a record
        record_code: the record code
        ti_dict: {ti_code : ti_value}
        """
        # TODO: setTecnicalInformation
        pass

    def changeCode(self, record_code, new_record_code):
        """changeCode(self, record_code, new_record_code):
        
        Change the record code for a new recor code.
        """
        if self.hasRecord(record_code) and not self.hasRecord(new_record_code):
            _record = self.__records[code]
            _record.code = new_record_code
            _parents = _record.parents
            for _parent in _parents:
                _decomposition_list = self.__records[_parent].children
                for _decomposition in _decomposition_list:
                    if _decomposition.code == record_code:
                        _decomposition.code = new_record_code
                        break
            _children = self.getchildren(record_code)
            for _child in _children:
                _parents_list = self.__records[_child].parents
                for index in range(len(_parents_list)):
                    if _parents_list[index] == record_code:
                        _parents_list[index] = new_record_code
                        break
            self.__records[new_record_code] = _record
            del self.__records[record_code]
            # TODO: attachment files

    def addLabel(self, record_code, label):
        """addLabel(self, record_code, label)
        
        Add a label to a record
        """
        if not isinstance(label,str):
            raise ValueError( _("The label must be a string") )
        if self.hasRecord(record_code):
            _record = self.__records[record_code]
            _record.addLabel(label)
            if not label in self.__labels:
                self.__labels[label] = [record_code]
            else:
                _codes = self.__labels[label]
                if not record_code in _codes:
                    _codes.append(record_code)

    def setParametricSelectComment(self, record_code, comment):
        """setParametricSelectComment(self, record_code, comment)
        
        Sets Paramtric Record Select Comment
        """
        if not isinstance(record_code, str):
            raise ValueError( _("The record_code code must be a string") )
        if not isinstance(comment, str):
            raise ValueError( _("The parametric select comment must be a "\
                                "string") )
        if not self.hasRecord(record_code):
            print(utils.mapping(_("Error: The budget do not have the record "\
                "code $1 and can not be added the Parametric select comment: "\
                "$2"),
                (str(record_code), str(comment))).encode("utf8") )
            return
        _record = self.getRecord(record_code)
        if not isinstance(_record, ParametricRecord):
            print(utils.mapping(_("Error: The Record $1 is not a "\
                "Parametric Record and can not have Parametric comment"),
                (str(record_code),)).encode("utf8") )
        else:
            _record.select_comment = comment

    def setParametricSummary(self, record_code, summary):
        """setParametricSummary(self, record_code, summary)
        
        Sets parametric record summary
        """
        if not isinstance(record_code, str):
            raise ValueError( _("The record_code code must be a string") )
        if not isinstance(summary, str):
            raise ValueError( _("The summary record must be a string") )
        if not self.hasRecord(record_code):
            print(utils.mapping(_("Error: The budget do not have the record "\
                "code $1 and can not be seted the summary: $2"),
                (str(record_code), str(summary))).encode("utf8") )
            return
        _record = self.getRecord(record_code)
        if not isinstance(_record, ParametricRecord):
            print(utils.mapping(_("Error: The Record $1 is not a "\
                "Parametric Record and can not have Parametric summary"),
                (str(record_code),)).encode("utf8") )
        else:
            self.getRecord(record_code).parametric_summary = summary

    def setParametricText(self, record_code, text):
        """setParametricText(self, record_code, text)
        
        Sets parametric record text
        """
        if not isinstance(record_code, str):
            raise ValueError( _("The record_code code must be a string") )
        if not isinstance(text, str):
            raise ValueError( _("The text record must be a string") )
        if not self.hasRecord(record_code):
            print(utils.mapping(_("Error: The budget do not have the record "\
                "code $1 and can not be seted the text: $2"),
                (str(record_code), str(text))).encode("utf8") )
            return
        _record = self.getRecord(record_code)
        if not isinstance(_record, ParametricRecord):
            print(utils.mapping(_("Error: The Record $1 is not a "\
                "Parametric Record and can not have Parametric text"),
                (str(record_code),)).encode("utf8") )
        else:
            self.getRecord(record_code).parametric_text = text


class Office(object):
    """base.Office:
    
    Description:
        Office of a company
    Constructor:
        base.Office(type, subname, address, postal_code, town, province,
                         country, phone, fax, contact_person)
    Ancestry:
    +-- object
      +-- Office
    Atributes:
        "officeType" : type of Office
                       are defined:
                        "C"  Central.
                        "D"  Local Office.
                        "R"  performer.
        "subname" : name of Office or Performer
        "address" :
        "postal_code" :
        "town" :
        "province" :
        "country" :
        "phone" : list of phone numbers
        "fax" : list of fax numbers
        "contact_person" : name of contact person
        "values":
    Methods:
        __getstate__(self)
        __setstate__(self, tuple)
        __init__(self, measure, lines, label)
        {get/set}OfficeType
        {get/set}Subname
        {get/set}Address
        {get/set}PostalCode
        {get/set}Town
        {get/set}Province
        {get/set}Country
        {get/set}Phone
        {get/set}Fax
        {get/set}ContactPerson
        getValues
    """

    __slots__ = ["_Office__officeType",
                 "_Office__subname",
                 "_Office__address",
                 "_Office__postal_code",
                 "_Office__town",
                 "_Office__province",
                 "_Office__country",
                 "_Office__phone",
                 "_Office__fax",
                 "_Office__contact_person",
                 ]

    def __getstate__ (self):
        return ( self.__officeType,
                 self.__subname,
                 self.__address,
                 self.__postal_code,
                 self.__town,
                 self.__province,
                 self.__country,
                 self.__phone,
                 self.__fax,
                 self.__contact_person)

    def __setstate__(self,tuple):
        self.__officeType = tuple[0]
        self.__subname = tuple[1]
        self.__address = tuple[2]
        self.__postal_code = tuple[3]
        self.__town = tuple[4]
        self.__province = tuple[5]
        self.__country = tuple[6]
        self.__phone = tuple[7]
        self.__fax = tuple[8]
        self.__contact_person = tuple[9]

    def __init__(self, type_, subname, address, postal_code, town, province,
                 country, phone, fax, contact_person):
        self.officeType = type_
        self.subname = subname
        self.address = address
        self.postal_code = postal_code
        self.town = town
        self.province = province
        self.country = country
        self.phone = phone
        self.fax = fax
        self.contact_person = contact_person

    def getOfficeType(self):
        return self.__officeType

    def setOfficeType(self, type_):
        self.__officeType = type_

    def getSubname(self):
        return self.__subname

    def setSubname(self, subname):
        self.__subname = subname

    def getAddress(self):
        return self.__address

    def setAddress(self, address):
        self.__address = address

    def getPostalCode(self):
        return self.__postal_code

    def setPostalCode(self, postal_code):
        self.__postal_code = postal_code

    def getTown(self):
        return self.__town

    def setTown(self, town):
        self.__town = town

    def getProvince(self):
        return self.__province

    def setProvince(self, province):
        self.__province = province

    def getCountry(self):
        return self.__country

    def setCountry(self, country):
        self.__country = country

    def getPhone(self):
        return self.__phone

    def setPhone(self, phone):
        self.__phone = phone

    def getFax(self):
        return self.__fax

    def setFax(self, fax):
        self.__fax = fax

    def getContactPerson(self):
        return self.__contact_person

    def setContactPerson(self, contact_person):
        self.__contact_person = contact_person

    def getValues(self):
        return {"officeType": self.officeType,
                "subname": self.subname,
                "address": self.address,
                "postal code": self.postal_code,
                "town": self.town,
                "province": self.province,
                "country": self.country,
                "phone": self.phone,
                "fax": self.fax,
                "contact person": self.contact_person,
               }

    officeType = property(getOfficeType, setOfficeType, None,
    """Type of office
    """)
    subname = property(getSubname, setSubname, None,
    """Name of office
    """)
    address = property(getAddress, setAddress, None,
    """Adress
    """)
    postal_code = property(getPostalCode, setPostalCode, None,
    """Postal code
    """)
    town = property(getTown, setTown, None,
    """Town
    """)
    province = property(getProvince, setProvince, None,
    """Province
    """)
    country = property(getCountry, setCountry, None,
    """Country
    """)
    phone = property(getPhone, setPhone, None,
    """Phone numbers
    """)
    fax = property(getFax, setFax, None,
    """Fax numbers
    """)
    contact_person = property(getContactPerson, setContactPerson, None,
    """Contact Person
    """)
    values = property(getValues, None, None,
    """Dictionary with comapany values
    """)


class Company(object):
    """base.Company:
    
    Description:
        Company object
        __slots__ attribute, __getstate__ and __setstate__ method are defined
        to use less ram memory.
    Constructor:
        base.Company(code, summary, name, offices, cif, web, email)
    Ancestry:
    +-- object
      +-- Company
    Atributes:
        "code": code to indentifie the company in the buget
        "summary": short name
        "name": long name
        "offices": List of Offices
        "cif": CIF
        "web": web page
        "email": email
        "values": 
    Methods:
        __getstate__(self)
        __setstate__(self, tuple)
        __init__(self, measure, lines, label)
        {get/set}Code
        {get/set}Summary
        {get/set}Name
        {get/set}Offices
        {get/set}Cif
        {get/set}Web
        {get/set}Email
        getValues
    """

    __slots__ = ["_Company__code",
                 "_Company__summary",
                 "_Company__name",
                 "_Company__offices",
                 "_Company__cif",
                 "_Company__web",
                 "_Company__email",
                 ]

    def __getstate__ (self):
        return ( self.__code,
                 self.__summary,
                 self.__name,
                 self.__offices,
                 self.__cif,
                 self.__web,
                 self.__email)

    def __setstate__(self,tuple):
        self.__code = tuple[0]
        self.__summary = tuple[1]
        self.__name = tuple[2]
        self.__offices = tuple[3]
        self.__cif = tuple[4]
        self.__web = tuple[5]
        self.__email = tuple[6]

    def __init__(self, code, summary, name, offices, cif, web, email):
        self.code = code
        self.summary = summary
        self.name = name
        self.offices = offices
        self.cif = cif
        self.web = web
        self.email = email

    def getCode(self):
        return self.__code

    def setCode(self, code):
        self.__code = code

    def getSummary(self):
        return self.__summary

    def setSummary(self, summary):
        self.__summary = summary

    def getName(self):
        return self.__name

    def setName(self, name):
        self.__name = name

    def getOffices(self):
        return self.__offices

    def setOffices(self, offices):
        self.__offices = offices

    def getCif(self):
        return self.__cif

    def setCif(self, cif):
        self.__cif = cif

    def getWeb(self):
        return self.__web

    def setWeb(self, web):
        self.__web = web

    def getEmail(self):
        return self.__email

    def setEmail(self, email):
        self.__email = email

    def getValues(self):
        return {"code": self.code,
                "summary": self.summary,
                "name": self.name,
                "cif": self.cif,
                "web": self.web,
                "email": self.email}

    code = property(getCode, setCode, None,
    """Company code 
    """)
    summary = property(getSummary, setSummary, None,
    """Company summary
    """)
    name = property(getName, setName, None,
    """Company name
    """)
    offices = property(getOffices, setOffices, None,
    """List of Offices
    """)
    cif = property(getCif, setCif, None,
    """CIF
    """)
    web = property(getWeb, setWeb, None,
    """Web page
    """)
    email = property(getEmail, setEmail, None,
    """Email
    """)
    values = property(getValues, None, None,
    """Dictionary with comapany values
    """)


class File(object):
    """base.Company:
    
    Description:
        File object
    Constructor:
        base.File(name, type_, description)
    Ancestry:
    +-- object
      +-- File
    Atributes:
        "name": name
        "fileType": type of file
        "description": description file
    Methods:
        __getstate__(self)
        __setstate__(self, tuple)
        __init__(self, path,type_, description)
        {get/set}Name
        {get/set}FileType
        {get/set}Description
        getValues
    """

    __slots__ = ["_File__name",
                 "_File__fileType",
                 "_File__description",
                 ]

    def __getstate__ (self):
        return (self.__name,
                self.__description,
                self.__fileType,
               )

    def __setstate__(self,tuple):
        self.__name = tuple[0]
        self.__fileType = tuple[1]
        self.__description = tuple[2]

    def __init__(self, name, type_, description):
        self.name = name
        self.fileType = type_
        self.description = description

    def getName(self):
        return self.__name

    def setName(self, name):
        self.__name = name

    def getFileType(self):
        return self.__fileType

    def setFileType(self, type_):
        self.__fileType = type_

    def getDescription(self):
        return self.__description

    def setDescription(self, description):
        self.__description = description

    def getValues(self):
        return {"name": self.name,
                "fileType": self.fileType,
                "description": self.description,
               }

    name = property(getName, setName, None,
    """File name
    """)
    fileType = property(getFileType, setFileType, None,
    """FileType
    """)
    description = property(getDescription, setDescription, None,
    """File description
    """)
    values = property(getValues, None, None,
    """Dictionary with file values
    """)


class RecordType(object):
    """base.RecordType:
    
    Description:
        Record Type object
        "hierarchy":
           -1 -> temporarily unfixed
            0 -> root
            1 -> Chapter/Subchapter
            2 -> Other
        "type" and "subtype":
            0 Without classifying
               EA  Auxiliary element
               EU  Unitary element
               EC  Complex element
               EF  Functional element
               OB  Construction site
               PA  Cost overrun
               PU  Unitary budget
            1 Labourforce 
               H   Labourforce
            2 Machinery and auxiliary equipment
               Q   Machinery
               %   Auxiliary equipment
            3 Building materials
               MC  Cement
               MCr Ceramic
               MM  Wood
               MS  Iron and steel
               ME  Energy
               MCu Copper
               MAl Aluminium
               ML  Bonding agents
               M   Others materials
          Hierarchy           type  subtype
            0->root         -> 0 -> None,OB
            1->[sub]chapter -> 0 -> None,PU
            2->Other        -> 0 -> None,EA,EU,EC,EF,PA
                               1 -> None,H
                               2 -> None,Q,%
                               3 -> None,MC,MCr,MM,MS,ME,MCu,Mal,ML,M
    Constructor:
        base.File(hierarchy,type_,subtype)
    Ancestry:
    +-- object
      +-- RecordType
    Atributes:
        "hierarchy": hierarchy
        "type": type
        "subtype": subtype
    Methods:
        __getstate__(self)
        __setstate__(self, tuple)
        __init__(self, hierarchy, type, subtype)
        {get/set}Hierarchy
        {get/set}Type
        {get/set}Subtype
    """

    __slots__ = ["_RecordType__hierarchy",
                 "_RecordType__type",
                 "_RecordType__subtype",
                 ]

    def __getstate__ (self):
        return (self.__hierarchy,
                self.__type,
                self.__subtype,
               )

    def __setstate__(self,tuple):
        self.__hierarchy = tuple[0]
        self.__type = tuple[1]
        self.__subtype = tuple[2]

    def __init__(self, hierarchy, type_, subtype):
        self.hierarchy = hierarchy
        self.type = type_
        self.subtype = subtype

    def getHierarchy(self):
        return self.__hierarchy

    def setHierarchy(self, hierarchy):
        if not hierarchy in [-1, 0 , 1 ,2, ""]:
            raise ValueError( utils.mapping(_("Invalid Hierarchy ($1) "\
                  "The hierarchy must be -1, 0, 1, 2"), (str(hierarchy),)) )
        elif hierarchy == "":
            print("Hierarchy temporarily set to an empty string".encode("utf8"))
        #TODO Check empty Hierarchy in Generic.fiebdc.Read._testBudget
        self.__hierarchy = hierarchy

    def getType(self):
        return self.__type

    def setType(self, type_):
        if not type_ in  ["", 0, 1, 2, 3] :
            raise ValueError( utils.mapping(_("Invalid type ($1),"\
                  "the type must be (empty string,0,1,2,3)"),(str(type_)),) )
        self.__type = type_

    def getSubtype(self):
        return self.__subtype

    def setSubtype(self, subtype):
        if not subtype in ["", "OB", "PU", "EA", "EU", "EC", "EF", "PA", "H",
                           "Q", "%", "MC", "MCr", "MM", "MS", "ME", "MCu",
                           "Mal","ML","M"]:
            raise ValueError( utils.mapping(_("Invalid subtype ($1), The "\
                  "subtype must one in (empty string, EA, "\
                  "EU, EC, EF, OB, PA, PU, H, Q, %, MC, MCr, "\
                  "MM, MS, ME, MCu, MAl, ML, M)"), (str(subtype),)) )
        self.__subtype = subtype

    hierarchy = property(getHierarchy, setHierarchy, None,
    """Record Hierarchy
           -1 -> temporarily unfixed
            0 -> root
            1 -> Chapter/Subchapter
            2 -> Other
    """)
    type = property(getType, setType, None,
    """Record Type
            0 Without classifying
            1 Labourforce 
            2 Machinery and auxiliary equipment
            3 Building materials
    """)
    subtype = property(getSubtype, setSubtype, None,
    """Record Subtype
            None
            EA  Auxiliary element
            EU  Unitary element
            EC  Complex element
            EF  Functional element
            OB  Construction site
            PA  Cost overrun
            PU  Unitary budget
            H   Labourforce
            Q   Machinery
            %   Auxiliary equipment
            MC  Cement
            MCr Ceramic
            MM  Wood
            MS  Iron and steel
            ME  Energy
            MCu Copper
            MAl Aluminium
            ML  Bonding agents
            M   Others materials
    """)