Ejemplo n.º 1
0
class ElectronicMail(ModelSQL, ModelView):
    "E-mail"
    __name__ = 'electronic_mail'

    mailbox = fields.Many2One('electronic_mail.mailbox',
                              'Mailbox',
                              required=True)
    from_ = fields.Char('From')
    sender = fields.Char('Sender')
    to = fields.Char('To')
    cc = fields.Char('CC')
    bcc = fields.Char('BCC')
    subject = fields.Char('Subject')
    date = fields.DateTime('Date')
    message_id = fields.Char('Message-ID', help='Unique Message Identifier')
    in_reply_to = fields.Char('In-Reply-To')
    headers = fields.One2Many('electronic_mail.header', 'electronic_mail',
                              'Headers')
    digest = fields.Char('MD5 Digest', size=32)
    collision = fields.Integer('Collision')
    email = fields.Function(fields.Binary('Email'), 'get_email', 'set_email')
    flag_seen = fields.Boolean('Seen')
    flag_answered = fields.Boolean('Answered')
    flag_flagged = fields.Boolean('Flagged')
    flag_draft = fields.Boolean('Draft')
    flag_recent = fields.Boolean('Recent')
    size = fields.Integer('Size')
    mailbox_owner = fields.Function(fields.Many2One('res.user', 'Owner'),
                                    'get_mailbox_owner',
                                    searcher='search_mailbox_owner')
    mailbox_read_users = fields.Function(fields.One2Many(
        'res.user', None, 'Read Users'),
                                         'get_mailbox_users',
                                         searcher='search_mailbox_users')
    mailbox_write_users = fields.Function(fields.One2Many(
        'res.user', None, 'Write Users'),
                                          'get_mailbox_users',
                                          searcher='search_mailbox_users')

    @staticmethod
    def default_collision():
        return 0

    @staticmethod
    def default_flag_seen():
        return False

    @staticmethod
    def default_flag_answered():
        return False

    @staticmethod
    def default_flag_flagged():
        return False

    @staticmethod
    def default_flag_recent():
        return False

    @classmethod
    def get_mailbox_owner(cls, mails, name):
        "Returns owner of mailbox"
        return dict([(mail.id, mail.mailbox.user.id) for mail in mails])

    @classmethod
    def get_mailbox_users(cls, mails, name):
        assert name in ('mailbox_read_users', 'mailbox_write_users')
        res = {}
        for mail in mails:
            if name == 'mailbox_read_users':
                res[mail.id] = [x.id for x in mail.mailbox['read_users']]
            else:
                res[mail.id] = [x.id for x in mail.mailbox['write_users']]
        return res

    @classmethod
    def search_mailbox_owner(cls, name, clause):
        return [('mailbox.user', ) + clause[1:]]

    @classmethod
    def search_mailbox_users(cls, name, clause):
        return [('mailbox.' + name[8:], ) + clause[1:]]

    def _get_email(self):
        """
        Returns the email object from reading the FS
        """
        db_name = Transaction().cursor.dbname
        value = u''
        if self.digest:
            filename = self.digest
            if self.collision:
                filename = filename + '-' + str(self.collision)
            filename = os.path.join(CONFIG['data_path'], db_name, 'email',
                                    filename[0:2], filename)
            try:
                with open(filename, 'r') as file_p:
                    value = file_p.read()
            except IOError:
                pass
        return value

    @classmethod
    def get_email(cls, mails, name):
        """Fetches email from the data_path as email object
        """
        result = {}
        for mail in mails:
            result[mail.id] = base64.encodestring(mail._get_email()) or False
        return result

    @classmethod
    def set_email(cls, mails, name, data):
        """Saves an email to the data path

        :param data: Email as string
        """
        if data is False or data is None:
            return
        db_name = Transaction().cursor.dbname
        # Prepare Directory <DATA PATH>/<DB NAME>/email
        directory = os.path.join(CONFIG['data_path'], db_name)
        if not os.path.isdir(directory):
            os.makedirs(directory, 0770)
        digest = cls.make_digest(data)
        directory = os.path.join(directory, 'email', digest[0:2])
        if not os.path.isdir(directory):
            os.makedirs(directory, 0770)
        # Filename <DIRECTORY>/<DIGEST>
        filename = os.path.join(directory, digest)
        collision = 0

        if not os.path.isfile(filename):
            # File doesnt exist already
            with open(filename, 'w') as file_p:
                file_p.write(data)
        else:
            # File already exists, may be its the same email data
            # or maybe different.

            # Case 1: If different: we have to write file with updated
            # Collission index

            # Case 2: Same file: Leave it as such
            with open(filename, 'r') as file_p:
                data2 = file_p.read()
            if data != data2:
                cursor = Transaction().cursor
                cursor.execute(
                    'SELECT DISTINCT(collision) FROM electronic_mail '
                    'WHERE digest = %s AND collision !=0 '
                    'ORDER BY collision', (digest, ))
                collision2 = 0
                for row in cursor.fetchall():
                    collision2 = row[0]
                    filename = os.path.join(directory,
                                            digest + '-' + str(collision2))
                    if os.path.isfile(filename):
                        with open(filename, 'r') as file_p:
                            data2 = file_p.read()
                        if data == data2:
                            collision = collision2
                            break
                if collision == 0:
                    collision = collision2 + 1
                    filename = os.path.join(directory,
                                            digest + '-' + str(collision))
                    with open(filename, 'w') as file_p:
                        file_p.write(data)
        cls.write(mails, {'digest': digest, 'collision': collision})

    @classmethod
    def make_digest(cls, data):
        """
        Returns a digest from the mail

        :param data: Data String
        :return: Digest
        """
        if hashlib:
            digest = hashlib.md5(data).hexdigest()
        else:
            digest = md5.new(data).hexdigest()
        return digest

    @classmethod
    def create_from_email(cls, mail, mailbox):
        """
        Creates a mail record from a given mail
        :param mail: email object
        :param mailbox: ID of the mailbox
        """
        Header = Pool().get('electronic_mail.header')
        email_date = mail.get('date') and datetime.fromtimestamp(
            mktime(parsedate(mail.get('date'))))
        values = {
            'mailbox': mailbox,
            'from_': mail.get('from'),
            'sender': mail.get('sender'),
            'to': mail.get('to'),
            'cc': mail.get('cc'),
            'bcc': mail.get('bcc'),
            'subject': mail.get('subject'),
            'date': email_date,
            'message_id': mail.get('message-id'),
            'in_reply_to': mail.get('in-reply-to'),
            'email': mail.as_string(),
            'size': getsizeof(mail.as_string()),
        }
        mail_created = cls.create([values])[0]
        Header.create_from_email(mail, mail_created.id)
        return mail_created
Ejemplo n.º 2
0
class Sample(metaclass=PoolMeta):
    __name__ = 'lims.sample'

    plant = fields.Function(fields.Many2One('lims.plant', 'Plant'),
        'get_plant', searcher='search_plant')
    equipment = fields.Many2One('lims.equipment', 'Equipment',
        domain=['OR', ('id', '=', Eval('equipment')),
            ('party', '=', Eval('party'))],
        depends=['party'], select=True)
    equipment_template = fields.Function(fields.Many2One(
        'lims.equipment.template', 'Equipment Template'),
        'get_equipment_field')
    equipment_model = fields.Function(fields.Char('Equipment Model'),
        'get_equipment_field')
    equipment_serial_number = fields.Function(fields.Char(
        'Equipment Serial Number'), 'get_equipment_field')
    equipment_name = fields.Function(fields.Char(
        'Equipment Name'), 'get_equipment_field')
    component = fields.Many2One('lims.component', 'Component',
        domain=['OR', ('id', '=', Eval('component')),
            ('equipment', '=', Eval('equipment'))],
        depends=['equipment'])
    comercial_product = fields.Many2One('lims.comercial.product',
        'Comercial Product')
    ind_sampling_date = fields.Date('Sampling date')
    ind_volume = fields.Float('Received volume')
    sampling_type = fields.Many2One('lims.sampling.type',
        'Sampling Type')
    ind_operational_detail = fields.Text('Operational detail')
    ind_work_environment = fields.Text('Work environment')
    ind_analysis_reason = fields.Text('Reason for analysis')
    missing_data = fields.Boolean('Missing data')
    attributes_domain = fields.Function(fields.Many2Many(
        'lims.sample.attribute', None, None, 'Attributes domain'),
        'on_change_with_attributes_domain')
    sample_photo = fields.Binary('Sample Photo',
        file_id='sample_photo_id', store_prefix='sample')
    sample_photo_id = fields.Char('Sample Photo ID', readonly=True)
    label_photo = fields.Binary('Label Photo',
        file_id='label_photo_id', store_prefix='sample')
    label_photo_id = fields.Char('Label Photo ID', readonly=True)
    oil_added = fields.Float('Liters Oil added')
    ind_equipment = fields.Integer('Equipment')
    ind_equipment_uom = fields.Selection([
        ('hs', 'Hs.'),
        ('km', 'Km.'),
        ], 'UoM', sort=False)
    ind_component = fields.Integer('Component')
    ind_component_uom = fields.Selection([
        ('hs', 'Hs.'),
        ('km', 'Km.'),
        ], 'UoM', sort=False)
    ind_oil = fields.Integer('Oil')
    ind_oil_uom = fields.Selection([
        ('hs', 'Hs.'),
        ('km', 'Km.'),
        ], 'UoM', sort=False)
    oil_changed = fields.Selection([
        (None, '-'),
        ('yes', 'Yes'),
        ('no', 'No'),
        ], 'Did change Oil?', sort=False)
    oil_changed_string = oil_changed.translated('oil_changed')
    oil_filter_changed = fields.Selection([
        (None, '-'),
        ('yes', 'Yes'),
        ('no', 'No'),
        ], 'Did change Oil Filter?', sort=False)
    oil_filter_changed_string = oil_filter_changed.translated(
        'oil_filter_changed')
    air_filter_changed = fields.Selection([
        (None, '-'),
        ('yes', 'Yes'),
        ('no', 'No'),
        ], 'Did change Air Filter?', sort=False)
    air_filter_changed_string = air_filter_changed.translated(
        'air_filter_changed')
    edition_log = fields.One2Many('lims.sample.edition.log', 'sample',
        'Edition log', readonly=True)

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().connection.cursor()
        table_h = cls.__table_handler__(module_name)
        sample = cls.__table__()

        super().__register__(module_name)

        if table_h.column_exist('changed_oil'):
            cursor.execute(*sample.update([sample.oil_changed],
                [Case((sample.changed_oil == Literal(True),
                    'yes'), else_='no')]))
            table_h.drop_column('changed_oil')
        if table_h.column_exist('changed_oil_filter'):
            cursor.execute(*sample.update([sample.oil_filter_changed],
                [Case((sample.changed_oil_filter == Literal(True),
                    'yes'), else_='no')]))
            table_h.drop_column('changed_oil_filter')
        if table_h.column_exist('changed_air_filter'):
            cursor.execute(*sample.update([sample.air_filter_changed],
                [Case((sample.changed_air_filter == Literal(True),
                    'yes'), else_='no')]))
            table_h.drop_column('changed_air_filter')
        if table_h.column_exist('hours_equipment'):
            cursor.execute(*sample.update([sample.ind_equipment],
                [sample.hours_equipment]))
            table_h.drop_column('hours_equipment')
        if table_h.column_exist('hours_component'):
            cursor.execute(*sample.update([sample.ind_component],
                [sample.hours_component]))
            table_h.drop_column('hours_component')
        if table_h.column_exist('hours_oil'):
            cursor.execute(*sample.update([sample.ind_oil],
                [sample.hours_oil]))
            table_h.drop_column('hours_oil')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.product_type.states['readonly'] = Bool(Eval('component'))
        if 'component' not in cls.product_type.depends:
            cls.product_type.depends.append('component')
        cls.matrix.states['readonly'] = Bool(Eval('comercial_product'))
        if 'comercial_product' not in cls.matrix.depends:
            cls.matrix.depends.append('comercial_product')
        cls.attributes.domain = [('id', 'in', Eval('attributes_domain'))]
        if 'attributes_domain' not in cls.attributes.depends:
            cls.attributes.depends.append('attributes_domain')

    @staticmethod
    def default_ind_equipment_uom():
        return 'hs'

    @staticmethod
    def default_ind_component_uom():
        return 'hs'

    @staticmethod
    def default_ind_oil_uom():
        return 'hs'

    @fields.depends('equipment', 'component')
    def on_change_equipment(self):
        if not self.equipment and self.component:
            self.component = None

    @fields.depends('component')
    def on_change_component(self):
        if self.component:
            if self.component.product_type:
                self.product_type = self.component.product_type.id
            if self.component.comercial_product:
                self.comercial_product = self.component.comercial_product.id
                self.on_change_comercial_product()

    @fields.depends('comercial_product')
    def on_change_comercial_product(self):
        if self.comercial_product and self.comercial_product.matrix:
            self.matrix = self.comercial_product.matrix.id

    @fields.depends('product_type', '_parent_product_type.attribute_set')
    def on_change_with_attributes_domain(self, name=None):
        pool = Pool()
        SampleAttributeAttributeSet = pool.get(
            'lims.sample.attribute-attribute.set')
        attribute_set = None
        if self.product_type and self.product_type.attribute_set:
            attribute_set = self.product_type.attribute_set.id
        res = SampleAttributeAttributeSet.search([
            ('attribute_set', '=', attribute_set),
            ])
        return [x.attribute.id for x in res]

    @classmethod
    def get_plant(cls, samples, name):
        result = {}
        for s in samples:
            result[s.id] = s.equipment and s.equipment.plant.id or None
        return result

    @classmethod
    def search_plant(cls, name, clause):
        return [('equipment.plant',) + tuple(clause[1:])]

    def _order_equipment_field(name):
        def order_field(tables):
            Equipment = Pool().get('lims.equipment')
            field = Equipment._fields[name]
            table, _ = tables[None]
            equipment_tables = tables.get('equipment')
            if equipment_tables is None:
                equipment = Equipment.__table__()
                equipment_tables = {
                    None: (equipment, equipment.id == table.equipment),
                    }
                tables['equipment'] = equipment_tables
            return field.convert_order(name, equipment_tables, Equipment)
        return staticmethod(order_field)
    order_plant = _order_equipment_field('plant')

    @classmethod
    def get_equipment_field(cls, samples, names):
        result = {}
        for name in names:
            result[name] = {}
            if cls._fields[name]._type == 'many2one':
                for s in samples:
                    field = getattr(s.equipment, name.replace(
                        'equipment_', ''), None)
                    result[name][s.id] = field.id if field else None
            else:
                for s in samples:
                    result[name][s.id] = getattr(s.equipment, name.replace(
                        'equipment_', ''), None)
        return result

    @classmethod
    def order_component(cls, tables):
        Component = Pool().get('lims.component')
        kind_field = Component._fields['kind']
        location_field = Component._fields['location']
        sample, _ = tables[None]
        component_tables = tables.get('component')
        if component_tables is None:
            component = Component.__table__()
            component_tables = {
                None: (component, component.id == sample.component),
                }
            tables['component'] = component_tables
        order = (
            kind_field.convert_order('kind',
            component_tables, Component) +
            location_field.convert_order('location',
            component_tables, Component))
        return order

    @classmethod
    def _confirm_samples(cls, samples):
        TaskTemplate = Pool().get('lims.administrative.task.template')
        for sample in samples:
            if not sample.component or not sample.comercial_product:
                continue
            if sample.component.comercial_product != sample.comercial_product:
                sample.component.comercial_product = sample.comercial_product
                sample.component.save()
        TaskTemplate.create_tasks('sample_missing_data',
            cls._for_task_missing_data(samples))
        TaskTemplate.create_tasks('sample_insufficient_volume',
            cls._for_task_required_volume(samples))

    @classmethod
    def _for_task_missing_data(cls, samples):
        AdministrativeTask = Pool().get('lims.administrative.task')
        res = []
        for sample in samples:
            if not sample.missing_data:
                continue
            if AdministrativeTask.search([
                    ('type', '=', 'sample_missing_data'),
                    ('origin', '=', '%s,%s' % (cls.__name__, sample.id)),
                    ('state', 'not in', ('done', 'discarded')),
                    ]):
                continue
            res.append(sample)
        return res

    @classmethod
    def _for_task_required_volume(cls, samples):
        pool = Pool()
        EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
        AdministrativeTask = pool.get('lims.administrative.task')
        res = []
        for sample in samples:
            received_volume = sample.ind_volume or 0
            analysis_detail = EntryDetailAnalysis.search([
                ('sample', '=', sample.id)])
            for detail in analysis_detail:
                received_volume -= (detail.analysis.ind_volume or 0)
            if received_volume >= 0:
                continue
            if AdministrativeTask.search([
                    ('type', '=', 'sample_insufficient_volume'),
                    ('origin', '=', '%s,%s' % (cls.__name__, sample.id)),
                    ('state', 'not in', ('done', 'discarded')),
                    ]):
                continue
            res.append(sample)
        return res

    @classmethod
    def delete(cls, samples):
        AdministrativeTask = Pool().get('lims.administrative.task')
        for sample in samples:
            tasks = AdministrativeTask.search([
                ('origin', '=', '%s,%s' % (cls.__name__, sample.id)),
                ('state', 'not in', ('done', 'discarded')),
                ])
            if tasks:
                AdministrativeTask.write(tasks, {'state': 'draft'})
                AdministrativeTask.delete(tasks)
        super().delete(samples)
Ejemplo n.º 3
0
class Employee(metaclass=PoolMeta):
    __name__ = 'company.employee'
    photo = fields.Binary('Photo', file_id='photo_id')
    photo_id = fields.Char('Photo id', states={'invisible': True})
    position = fields.Many2One('rrhh.position', 'Position', required=True,
        domain=[('department.company', '=', Eval('company'))],
        depends=['company'])
    department = fields.Function(fields.Char('Department'), 'get_department')
    contract_type = fields.Many2One('rrhh.contract.type',
        'Contract type', required=True,
        domain=[('company', '=', Eval('company'))],
        depends=['company'])
    payment_type = fields.Many2One('rrhh.payment.type',
        'Payment type', required=True)
    instruction_level = fields.Many2One('rrhh.instruction.level',
        'Instruction level', required=True)
    documents = fields.One2Many('rrhh.document', 'employee', 'Documents')
    qualifications = fields.One2Many('rrhh.qualification',
        'employee', 'Qualification')
    marital_status = fields.Function(fields.Char('Legal State'),
        'get_marital_status')
    gender = fields.Function(fields.Char('Gender'),
        'get_gender')
    birth_date = fields.Function(fields.Date('Birth date'),
        'get_birth_date')
    birth_country = fields.Many2One('country.country',
        'Country of birth', states={'required': True})
    nationality = fields.Many2One('country.country',
        'Nationality', states={'required': True})
    residence = fields.Many2One('country.country',
        'Country of residence', states={'required': True})
    age = fields.Function(fields.Char('Age'), 'get_age')
    dependents = fields.One2Many('rrhh.dependent', 'employee', 'Dependents')

    @classmethod
    def __register__(cls, module_name):
        super(Employee, cls).__register__(module_name)

        table = cls.__table_handler__(module_name)
        # To 5.2
        if table.column_exist('first_name'):
            table.drop_column('first_name')
            table.drop_column('middle_name')
            table.drop_column('last_name')

        # To 5.6.1
        if table.column_exist('birth_date'):
            table.drop_column('birth_date')
            table.drop_column('genre')
            table.drop_column('marital_status')

    @classmethod
    def __setup__(cls):
        super(Employee, cls).__setup__()

        cls.company.states['readonly'] = True

        cls.party.domain = [
            ('party_type', '=', 'person')
            ]

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def compute_age(party_dob):
        # TODO Translate with gettext
        if not party_dob:
            return 'No Birth date!'

        now = datetime.now()
        dob = datetime.strptime(str(party_dob), '%Y-%m-%d')
        delta = relativedelta(now, dob)

        years_months_days = str(delta.years) + 'y ' \
            + str(delta.months) + 'm ' \
            + str(delta.days) + 'd'

        return years_months_days

    def get_age(self, name):
        return self.compute_age(self.birth_date)

    def get_department(self, name):
        if self.position:
            return self.position.department.name

    def get_marital_status(self, name):
        return self._get_party_selection_string('person_legal_state')

    def get_gender(self, name):
        return self._get_party_selection_string('gender')

    def _get_party_selection_string(self, sel_name):
        pool = Pool()
        Trans = pool.get('ir.translation')
        Party = pool.get('party.party')
        if self.party:
            selection = getattr(Party, sel_name).selection
            sel = dict(selection)[getattr(self.party, sel_name)]
            lang = Transaction().context.get('language', None)
            if lang in (None, '', 'en'):
                return sel
            val = Trans.get_source(
                'party.party,' + sel_name,
                'selection',
                lang,
                sel)
            if not val:
                val = sel
            return val

    def get_birth_date(self, name):
        if self.party:
            return self.party.dob

    @fields.depends('position')
    def on_change_company(self):
        self.position = None
Ejemplo n.º 4
0
class NereidStaticFile(ModelSQL, ModelView):
    "Static files for Nereid"
    __name__ = "nereid.static.file"

    name = fields.Char('File Name', select=True, required=True)
    folder = fields.Many2One('nereid.static.folder',
                             'Folder',
                             select=True,
                             required=True)

    #: This function field returns the field contents. This is useful if the
    #: field is going to be displayed on the clients.
    file_binary = fields.Function(
        fields.Binary('File', filename='name'),
        'get_file_binary',
        'set_file_binary',
    )

    #: Full path to the file in the filesystem
    file_path = fields.Function(fields.Char('File Path'), 'get_file_path')

    #: URL that can be used to idenfity the resource. Note that the value
    #: of this field is available only when called within a request context.
    #: In other words the URL is valid only when called in a nereid request.
    url = fields.Function(fields.Char('URL'), 'get_url')

    # Sequence
    sequence = fields.Integer('Sequence', select=True)

    # File mimetype
    mimetype = fields.Function(fields.Char('Mimetype'), getter='get_mimetype')

    @classmethod
    def __setup__(cls):
        super(NereidStaticFile, cls).__setup__()
        cls._sql_constraints += [
            ('name_folder_uniq', 'UNIQUE(name, folder)',
             'The Name of the Static File must be unique in a folder.!'),
        ]
        cls._error_messages.update({
            'invalid_file_name':
            """Invalid file name:
                (1) '..' in file name (OR)
                (2) file name contains '/'""",
        })

    @staticmethod
    def default_sequence():
        return 10

    def get_mimetype(self, name):
        """
        This method detects and returns the mimetype for the static file.

        The python mimetypes module returns a tuple of the form -:

        >>> mimetypes.guess_type(file_name)
        (file_mimetype, encoding)

        which can then be used to fill the `mimetype` field. Some example types
        are -:
            * image/png
            * application/pdf
        etc.
        """
        return mimetypes.guess_type(self.name)[0]

    def get_url(self, name):
        """Return the url if within an active request context or return
        False values
        """
        if _request_ctx_stack.top is None:
            return None

        return url_for('nereid.static.file.send_static_file',
                       folder=self.folder.name,
                       name=self.name)

    @staticmethod
    def get_nereid_base_path():
        """
        Returns base path for nereid, where all the static files would be
        stored.

        By Default it is:

        <Tryton Data Path>/<Database Name>/nereid
        """
        cursor = Transaction().cursor
        return os.path.join(config.get('database', 'path'),
                            cursor.database_name, "nereid")

    def _set_file_binary(self, value):
        """
        Setter for static file that stores file in file system

        :param value: The value to set
        """
        file_binary = fields.Binary.cast(bytes(value))
        # If the folder does not exist, create it recursively
        directory = os.path.dirname(self.file_path)
        if not os.path.isdir(directory):
            os.makedirs(directory)
        with open(self.file_path, 'wb') as file_writer:
            file_writer.write(file_binary)

    @classmethod
    def set_file_binary(cls, files, name, value):
        """
        Setter for the functional binary field.

        :param files: Records
        :param name: Ignored
        :param value: The file buffer
        """
        for static_file in files:
            static_file._set_file_binary(value)

    def get_file_binary(self, name):
        '''
        Getter for the binary_file field. This fetches the file from the
        file system, coverts it to buffer and returns it.

        :param name: Field name
        :return: Bytes
        '''
        location = self.file_path
        with open(location, 'rb') as file_reader:
            return fields.Binary.cast(file_reader.read())

    def get_file_path(self, name):
        """
        Returns the full path to the file in the file system

        :param name: Field name
        :return: File path
        """
        return os.path.abspath(
            os.path.join(self.get_nereid_base_path(), self.folder.name,
                         self.name))

    @classmethod
    def validate(cls, files):
        """
        Validates the records.

        :param files: active record list of static files
        """
        super(NereidStaticFile, cls).validate(files)
        for file in files:
            file.check_file_name()

    def check_file_name(self):
        '''
        Check the validity of folder name
        Allowing the use of / or . will be risky as that could
        eventually lead to previlege escalation
        '''
        if ('..' in self.name) or ('/' in self.name):
            self.raise_user_error("invalid_file_name")

    @classmethod
    @route("/static-file/<folder>/<name>", methods=["GET"])
    def send_static_file(cls, folder, name):
        """
        Invokes the send_file method in nereid.helpers to send a file as the
        response to the request. The file is sent in a way which is as
        efficient as possible. For example nereid will use the X-Send_file
        header to make nginx send the file if possible.

        :param folder: name of the folder
        :param name: name of the file
        """
        # TODO: Separate this search and find into separate cached method

        files = cls.search([('folder.name', '=', folder), ('name', '=', name)])
        if not files:
            abort(404)
        return send_file(files[0].file_path)
Ejemplo n.º 5
0
class TemplateImages(sequence_ordered('sequence', 'Orden de Listado'),
                     ModelView, ModelSQL):
    'Product Template Images'
    __name__ = 'product.template.images'

    template = fields.Many2One('product.template', 'Producto')
    image_name = fields.Function(fields.Char('File Name'), 'get_image_name')
    image_id = fields.Char('File ID',
                           readonly=True,
                           states={'invisible': True})
    image = fields.Binary('Imagen',
                          filename='image_name',
                          file_id=file_id,
                          store_prefix=None)
    image_f = fields.Function(
        fields.Binary('Imagen',
                      filename='image_name',
                      file_id=file_id,
                      store_prefix=None), 'get_image_f')

    @classmethod
    def convert_photo(cls, data):
        if data and Image:
            image = Image.open(BytesIO(data))
            # if image._getexif():
            #     exif = dict((ExifTags.TAGS[k], v) for k, v in image._getexif().items() if k in ExifTags.TAGS)
            #     if exif.get('Orientation'):
            #         if exif.get('Orientation')%2==0:
            #             image = image.rotate(90, expand=True)
            image.thumbnail((388, 500), Image.ANTIALIAS)
            data = BytesIO()
            if not (image.format):
                image.save(data, 'JPEG')
            else:
                image.save(data, image.format)
            data = fields.Binary.cast(data.getvalue())
        return data

    @classmethod
    def write(cls, *args):
        actions = iter(args)
        args = []
        for product, vals in zip(actions, actions):
            if 'image' in vals:
                vals['image'] = cls.convert_photo(vals['image'])
            args.extend((product, vals))

        super(TemplateImages, cls).write(*args)

    @classmethod
    def create(cls, vlist):
        vlist = [x.copy() for x in vlist]
        for values in vlist:

            if 'image' in values:
                values['image'] = cls.convert_photo(values['image'])

        return super(TemplateImages, cls).create(vlist)

    def get_img(self):
        foto = None
        if self.image_id:
            return '/static/img2/' + self.image_id[:2] + '/' + self.image_id[
                2:4] + '/' + self.image_id

        if self.image:
            import base64
            foto = base64.b64encode(self.image).decode()
            return "data:image/JPEG;base64," + foto
        return None

    def get_image_f(self, name):
        if self.image:
            return self.image
        return None

    def get_image_name(self, name):
        file_name = ''
        if self.template:
            if self.template.rec_name:
                file_name = self.template.rec_name + ".jpg"
            return file_name

    def get_rec_name(self, name):
        if self.template:
            return self.template.name
Ejemplo n.º 6
0
class Attachment(ResourceMixin, ModelSQL, ModelView):
    "Attachment"
    __name__ = 'ir.attachment'
    name = fields.Char('Name', required=True)
    type = fields.Selection([
        ('data', 'Data'),
        ('link', 'Link'),
    ],
                            'Type',
                            required=True)
    description = fields.Text('Description')
    summary = fields.Function(fields.Char('Summary'), 'on_change_with_summary')
    link = fields.Char('Link',
                       states={
                           'invisible': Eval('type') != 'link',
                       },
                       depends=['type'])
    data = fields.Binary('Data',
                         filename='name',
                         file_id=file_id,
                         store_prefix=store_prefix,
                         states={
                             'invisible': Eval('type') != 'data',
                         },
                         depends=['type'])
    file_id = fields.Char('File ID', readonly=True)
    data_size = fields.Function(
        fields.Integer('Data size',
                       states={
                           'invisible': Eval('type') != 'data',
                       },
                       depends=['type']), 'get_size')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._order = [
            ('create_date', 'DESC'),
            ('id', 'DESC'),
        ]

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().connection.cursor()

        super(Attachment, cls).__register__(module_name)

        table = cls.__table_handler__(module_name)
        attachment = cls.__table__()

        # Migration from 4.0: merge digest and collision into file_id
        if table.column_exist('digest') and table.column_exist('collision'):
            cursor.execute(
                *attachment.update([attachment.file_id], [attachment.digest],
                                   where=(attachment.collision == 0)
                                   | (attachment.collision == Null)))
            cursor.execute(*attachment.update(
                [attachment.file_id],
                [Concat(Concat(attachment.digest, '-'), attachment.collision)],
                where=(attachment.collision != 0)
                & (attachment.collision != Null)))
            table.drop_column('digest')
            table.drop_column('collision')

        # Migration from 4.8: remove unique constraint
        table.drop_constraint('resource_name')

    @staticmethod
    def default_type():
        return 'data'

    def get_size(self, name):
        with Transaction().set_context({
                '%s.%s' % (self.__name__, name): 'size',
        }):
            record = self.__class__(self.id)
            return record.data

    @fields.depends('description')
    def on_change_with_summary(self, name=None):
        return firstline(self.description or '')

    @classmethod
    def fields_view_get(cls, view_id=None, view_type='form', level=None):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        if not view_id:
            if Transaction().context.get('preview'):
                view_id = ModelData.get_id('ir',
                                           'attachment_view_form_preview')
        return super().fields_view_get(view_id=view_id,
                                       view_type=view_type,
                                       level=level)
Ejemplo n.º 7
0
class ReportTemplateSection(ModelSQL, ModelView):
    'Report Template Section'
    __name__ = 'lims.report.template.section'
    _order_name = 'order'

    template = fields.Many2One('lims.report.template',
                               'Template',
                               ondelete='CASCADE',
                               select=True,
                               required=True)
    name = fields.Char('Name', required=True)
    data = fields.Binary('File',
                         filename='name',
                         required=True,
                         file_id='data_id',
                         store_prefix='results_report_template_section')
    data_id = fields.Char('File ID', readonly=True)
    position = fields.Selection([
        ('previous', 'Previous'),
        ('following', 'Following'),
    ],
                                'Position',
                                required=True)
    order = fields.Integer('Order')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._order.insert(0, ('order', 'ASC'))

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().connection.cursor()
        TableHandler = backend.TableHandler

        old_table_exist = TableHandler.table_exist(
            'lims_result_report_template_section')
        if old_table_exist:
            cursor.execute('ALTER TABLE '
                           'lims_result_report_template_section '
                           'RENAME TO lims_report_template_section ')
            cursor.execute('ALTER INDEX '
                           'lims_result_report_template_section_pkey '
                           'RENAME TO lims_report_template_section_pkey')
            cursor.execute(
                'ALTER INDEX '
                'lims_result_report_template_section_template_index '
                'RENAME TO lims_report_template_section_template_index')
            cursor.execute('ALTER SEQUENCE '
                           'lims_result_report_template_section_id_seq '
                           'RENAME TO lims_report_template_section_id_seq')

        super().__register__(module_name)

    @classmethod
    def validate(cls, sections):
        super().validate(sections)
        merger = PdfFileMerger(strict=False)
        for section in sections:
            filedata = BytesIO(section.data)
            try:
                merger.append(filedata)
            except PdfReadError:
                raise UserError(gettext('lims_report_html.msg_section_pdf'))
Ejemplo n.º 8
0
class People(ModelSQL, ModelView):
    "People"
    _name = "auroville.people"
    _description = __doc__

    #TODO uniq id
    asynctoid = fields.Char('Asyncto Id',
                            required=False,
                            select=False,
                            readonly=True)
    aurovillename = fields.Char('Auroville name',
                                required=True,
                                translate=False,
                                select=True)
    name = fields.Char('Given name', translate=False, select=True)
    surname = fields.Char('Surname', translate=False, select=True)
    telephone = fields.Char('Phone number', translate=False)
    email = fields.Char('Email', translate=False, select=True)
    contactperson = fields.Char('Contact person', translate=False, select=True)
    masterlistid = fields.Integer('Masterlist ID', readonly=True, select=True)

    date_birth = fields.Date('Date of birth')

    sex = fields.Selection([
        ('Male', 'Male'),
        ('Female', 'Female'),
    ],
                           'Sex',
                           required=True)

    photo = fields.Binary('Picture')

    nationality = fields.Many2One('auroville.nationality',
                                  'Nationality',
                                  select=True)
    age = fields.Function(fields.Integer('Age'), 'get_people_age')

    status = fields.Selection([
        ('Aurovilian', 'Aurovilian'),
        ('Newcomer', 'Newcomer'),
        ('Pre-Newcomer', 'Pre-Newcomer'),
        ('Volunteer', 'Volunteer'),
        ('Youth', 'Youth'),
        ('Child', 'Child'),
        ('Friend of Auroville', 'Friend of Auroville'),
        ('Guest', 'Guest'),
    ],
                              'Status',
                              required=True)

    user_id = fields.Many2One('res.user', 'User', required=True, readonly=True)

    def __init__(self):
        super(People, self).__init__()
        self._order.insert(0, ('masterlistid', 'ASC'))

    def get_people_age(self, ids, name):

        today = date.today()
        res = {}
        for human in self.browse(ids):
            born = human.date_birth
            if born:
                res[human.id] = today.year - born.year - int(
                    (today.month, today.day) < (born.month, born.day))
            else:
                res[human.id] = 0
        return res

    def default_user_id(self):
        user_obj = Pool().get('res.user')
        user = user_obj.browse(Transaction().user)
        return int(user.id)

    def default_sex(self):
        return 'Male'

    def default_status(self):
        return 'Aurovilian'

    def asyncto_sync(self):
        log = logging.getLogger('logfile')
        log.warn('Asyncto People sync')
        try:
            asyncto_obj = Pool().get('auroville.asyncto')
            people_obj = Pool().get('auroville.people')
            asyncto_ids = asyncto_obj.search([])
            for item in asyncto_obj.browse(asyncto_ids):
                people_ids = people_obj.search([
                    ('asynctoid', '=', item.asynctoid),
                ])
                data = {
                    'asynctoid': item.asynctoid,
                    'aurovillename': item.aurovillename,
                    'name': item.name,
                    'surname': item.surname,
                    'telephone': item.telephone,
                    'email': item.email,
                    'contactperson': item.contactperson,
                    'masterlistid': int(item.masterlistid)
                }
                if people_ids:
                    people_obj.write(people_ids, data)
                else:
                    people_obj.create(data)

                # Community

        except Exception as e:
            log.error(e)
Ejemplo n.º 9
0
class Binary(ModelSQL):
    'Binary'
    __name__ = 'test.binary'
    binary = fields.Binary('Binary')
Ejemplo n.º 10
0
class NereidStaticFile(ModelSQL, ModelView):
    "Static files for Nereid"
    __name__ = "nereid.static.file"

    name = fields.Char('File Name', select=True, required=True)
    folder = fields.Many2One('nereid.static.folder',
                             'Folder',
                             select=True,
                             required=True)
    type = fields.Selection([
        ('local', 'Local File'),
        ('remote', 'Remote File'),
    ], 'File Type')

    #: URL of the remote file if the :attr:`type` is remote
    remote_path = fields.Char('Remote File',
                              select=True,
                              translate=True,
                              states={
                                  'required': Equal(Eval('type'), 'remote'),
                                  'invisible':
                                  Not(Equal(Eval('type'), 'remote'))
                              })

    #: This function field returns the field contents. This is useful if the
    #: field is going to be displayed on the clients.
    file_binary = fields.Function(
        fields.Binary('File', filename='name'),
        'get_file_binary',
        'set_file_binary',
    )

    #: Full path to the file in the filesystem
    file_path = fields.Function(fields.Char('File Path'), 'get_file_path')

    #: URL that can be used to idenfity the resource. Note that the value
    #: of this field is available only when called within a request context.
    #: In other words the URL is valid only when called in a nereid request.
    url = fields.Function(fields.Char('URL'), 'get_url')

    @classmethod
    def __setup__(cls):
        super(NereidStaticFile, cls).__setup__()
        cls._constraints += [
            ('check_file_name', 'invalid_file_name'),
        ]
        cls._sql_constraints += [
            ('name_folder_uniq', 'UNIQUE(name, folder)',
             'The Name of the Static File must be unique in a folder.!'),
        ]
        cls._error_messages.update({
            'invalid_file_name':
            """Invalid file name:
                (1) '..' in file name (OR)
                (2) file name contains '/'""",
        })

    @staticmethod
    def default_type():
        return 'local'

    def get_url(self, name):
        """Return the url if within an active request context or return
        False values
        """
        if _request_ctx_stack.top is None:
            return None

        if self.type == 'local':
            return url_for('nereid.static.file.send_static_file',
                           folder=self.folder.folder_name,
                           name=self.name)
        elif self.type == 'remote':
            return self.remote_path

    @staticmethod
    def get_nereid_base_path():
        """
        Returns base path for nereid, where all the static files would be
        stored.

        By Default it is:

        <Tryton Data Path>/<Database Name>/nereid
        """
        cursor = Transaction().cursor
        return os.path.join(CONFIG['data_path'], cursor.database_name,
                            "nereid")

    def _set_file_binary(self, value):
        """
        Setter for static file that stores file in file system

        :param value: The value to set
        """
        if self.type == 'local':
            file_binary = buffer(value)
            # If the folder does not exist, create it recursively
            directory = os.path.dirname(self.file_path)
            if not os.path.isdir(directory):
                os.makedirs(directory)
            with open(self.file_path, 'wb') as file_writer:
                file_writer.write(file_binary)

    @classmethod
    def set_file_binary(cls, files, name, value):
        """
        Setter for the functional binary field.

        :param files: Records
        :param name: Ignored
        :param value: The file buffer
        """
        for static_file in files:
            static_file._set_file_binary(value)

    def get_file_binary(self, name):
        '''
        Getter for the binary_file field. This fetches the file from the
        file system, coverts it to buffer and returns it.

        :param name: Field name
        :return: File buffer
        '''
        location = self.file_path if self.type == 'local' \
            else urllib.urlretrieve(self.remote_path)[0]
        with open(location, 'rb') as file_reader:
            return buffer(file_reader.read())

    def get_file_path(self, name):
        """
        Returns the full path to the file in the file system

        :param name: Field name
        :return: File path
        """
        return os.path.abspath(
                os.path.join(
                    self.get_nereid_base_path(),
                    self.folder.folder_name, self.name
                )) \
            if self.type == 'local' else self.remote_path

    def check_file_name(self):
        '''
        Check the validity of folder name
        Allowing the use of / or . will be risky as that could
        eventually lead to previlege escalation
        '''
        if ('..' in self.name) or ('/' in self.name):
            return False
        return True

    @classmethod
    def send_static_file(cls, folder, name):
        """
        Invokes the send_file method in nereid.helpers to send a file as the
        response to the request. The file is sent in a way which is as
        efficient as possible. For example nereid will use the X-Send_file
        header to make nginx send the file if possible.

        :param folder: folder_name of the folder
        :param name: name of the file
        """
        #TODO: Separate this search and find into separate cached method

        files = cls.search([('folder.folder_name', '=', folder),
                            ('name', '=', name)])
        if not files:
            abort(404)
        return send_file(files[0].file_path)
Ejemplo n.º 11
0
class Message(Workflow, ModelSQL, ModelView):
    'SEPA Message'
    __name__ = 'account.payment.sepa.message'
    _states = {
        'readonly': Eval('state') != 'draft',
    }
    _depends = ['state']
    message = fields.Binary('Message',
                            filename='filename',
                            file_id=file_id,
                            store_prefix=store_prefix,
                            states=_states,
                            depends=_depends)
    message_file_id = fields.Char("Message File ID", readonly=True)
    filename = fields.Function(fields.Char('Filename'), 'get_filename')
    type = fields.Selection([
        ('in', 'IN'),
        ('out', 'OUT'),
    ],
                            'Type',
                            required=True,
                            states=_states,
                            depends=_depends)
    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        select=True,
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=',
                      '!='), Eval('context', {}).get('company', -1)),
        ],
        states={
            'readonly': Eval('state') != 'draft',
        },
        depends=['state'])
    origin = fields.Reference('Origin',
                              selection='get_origin',
                              select=True,
                              states=_states,
                              depends=_depends)
    state = fields.Selection([
        ('draft', 'Draft'),
        ('waiting', 'Waiting'),
        ('done', 'Done'),
        ('canceled', 'Canceled'),
    ],
                             'State',
                             readonly=True,
                             select=True)

    @classmethod
    def __setup__(cls):
        super(Message, cls).__setup__()
        cls._transitions |= {
            ('draft', 'waiting'),
            ('waiting', 'done'),
            ('waiting', 'draft'),
            ('draft', 'canceled'),
            ('waiting', 'canceled'),
        }
        cls._buttons.update({
            'cancel': {
                'invisible': ~Eval('state').in_(['draft', 'waiting']),
                'depends': ['state'],
            },
            'draft': {
                'invisible': Eval('state') != 'waiting',
                'depends': ['state'],
            },
            'wait': {
                'invisible': Eval('state') != 'draft',
                'depends': ['state'],
            },
            'do': {
                'invisible': Eval('state') != 'waiting',
                'depends': ['state'],
            },
        })

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        cursor = Transaction().connection.cursor()
        pool = Pool()
        Group = pool.get('account.payment.group')

        super(Message, cls).__register__(module_name)

        # Migration from 3.2
        if TableHandler.table_exist(Group._table):
            group_table = Group.__table_handler__(module_name)
            if group_table.column_exist('sepa_message'):
                group = Group.__table__()
                table = cls.__table__()
                cursor.execute(
                    *group.select(group.id, group.sepa_message, group.company))
                for group_id, message, company_id in cursor.fetchall():
                    cursor.execute(*table.insert([
                        table.message, table.type, table.company, table.origin,
                        table.state
                    ], [[
                        message, 'out', company_id,
                        'account.payment.group,%s' % group_id, 'done'
                    ]]))
                group_table.drop_column('sepa_message')

    @staticmethod
    def default_type():
        return 'in'

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_state():
        return 'draft'

    def get_filename(self, name):
        pool = Pool()
        Group = pool.get('account.payment.group')
        if isinstance(self.origin, Group):
            return self.origin.rec_name + '.xml'

    @staticmethod
    def _get_origin():
        'Return list of Model names for origin Reference'
        return ['account.payment.group']

    @classmethod
    def get_origin(cls):
        IrModel = Pool().get('ir.model')
        models = cls._get_origin()
        models = IrModel.search([
            ('model', 'in', models),
        ])
        return [(None, '')] + [(m.model, m.name) for m in models]

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, messages):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('waiting')
    def wait(cls, messages):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def do(cls, messages):
        for message in messages:
            if message.type == 'in':
                message.parse()
            else:
                message.send()

    @classmethod
    @ModelView.button
    @Workflow.transition('canceled')
    def cancel(cls, messages):
        pass

    @staticmethod
    def _get_handlers():
        pool = Pool()
        Payment = pool.get('account.payment')
        return {
            'urn:iso:std:iso:20022:tech:xsd:camt.054.001.01':
            lambda f: CAMT054(f, Payment),
            'urn:iso:std:iso:20022:tech:xsd:camt.054.001.02':
            lambda f: CAMT054(f, Payment),
            'urn:iso:std:iso:20022:tech:xsd:camt.054.001.03':
            lambda f: CAMT054(f, Payment),
            'urn:iso:std:iso:20022:tech:xsd:camt.054.001.04':
            lambda f: CAMT054(f, Payment),
        }

    @staticmethod
    def get_namespace(message):
        f = BytesIO(message)
        for _, element in etree.iterparse(f, events=('start', )):
            tag = etree.QName(element)
            if tag.localname == 'Document':
                return tag.namespace

    def parse(self):
        f = BytesIO(self.message)
        namespace = self.get_namespace(self.message)
        handlers = self._get_handlers()
        if namespace not in handlers:
            raise  # TODO UserError
        handlers[namespace](f)

    def send(self):
        pass
Ejemplo n.º 12
0
class Party(ModelSQL, ModelView):
    "Party"
    __name__ = 'party.party'
    #avatar
    avatar = fields.Function(fields.Binary('Avatar',
                                           filename='avatar_filename',
                                           help='Avatar User Image'),
                             'get_avatar',
                             setter='set_avatar')
    avatar_filename = fields.Char('Avatar File Name', help='Avatar File Name')

    name = fields.Char('Name',
                       required=True,
                       select=True,
                       states=STATES,
                       depends=DEPENDS)
    code = fields.Char('Code',
                       required=True,
                       select=True,
                       states={
                           'readonly': Eval('code_readonly', True),
                       },
                       depends=['code_readonly'])
    code_readonly = fields.Function(fields.Boolean('Code Readonly'),
                                    'get_code_readonly')
    lang = fields.Many2One("ir.lang",
                           'Language',
                           states=STATES,
                           depends=DEPENDS)
    vat_number = fields.Char('VAT Number',
                             help="Value Added Tax number",
                             states={
                                 'readonly': ~Eval('active', True),
                                 'required': Bool(Eval('vat_country')),
                             },
                             depends=['active', 'vat_country'])
    vat_country = fields.Selection(
        VAT_COUNTRIES,
        'VAT Country',
        states=STATES,
        depends=DEPENDS,
        help="Setting VAT country will enable validation of the VAT number.",
        translate=False)
    vat_code = fields.Function(fields.Char('VAT Code'),
                               'on_change_with_vat_code',
                               searcher='search_vat_code')
    addresses = fields.One2Many('party.address',
                                'party',
                                'Addresses',
                                states=STATES,
                                depends=DEPENDS)
    contact_mechanisms = fields.One2Many('party.contact_mechanism',
                                         'party',
                                         'Contact Mechanisms',
                                         states=STATES,
                                         depends=DEPENDS)
    categories = fields.Many2Many('party.party-party.category',
                                  'party',
                                  'category',
                                  'Categories',
                                  states=STATES,
                                  depends=DEPENDS)
    active = fields.Boolean('Active', select=True)
    full_name = fields.Function(fields.Char('Full Name'), 'get_full_name')
    phone = fields.Function(fields.Char('Phone'), 'get_mechanism')
    mobile = fields.Function(fields.Char('Mobile'), 'get_mechanism')
    fax = fields.Function(fields.Char('Fax'), 'get_mechanism')
    email = fields.Function(fields.Char('E-Mail'), 'get_mechanism')
    website = fields.Function(fields.Char('Website'), 'get_mechanism')
    # photo = fields.Binary('Picture')
    # description = fields.Text("Description", translate=True, states=STATES,
    #    depends=DEPENDS)

    @classmethod
    def __setup__(cls):
        super(Party, cls).__setup__()
        cls._sql_constraints = [('code_uniq', 'UNIQUE(code)',
                                 'The code of the party must be unique.')]
        cls._error_messages.update({
            'invalid_vat': ('Invalid VAT number "%(vat)s" on party '
                            '"%(party)s".'),
        })
        cls._order.insert(0, ('name', 'ASC'))

    def get_avatar(self, name):
        db_name = Transaction().cursor.dbname
        filename = self.avatar_filename
        if not filename:
            return None
        filename = os.path.join(config.get('database',
                                           'path'), db_name, 'user', 'avatar',
                                filename[0:2], filename[2:4], filename)

        value = None
        try:
            with open(filename, 'rb') as file_p:
                value = buffer(file_p.read())
        except IOError:
            pass
        return value

    @classmethod
    def set_avatar(cls, users, name, value):
        if value is None:
            return
        if not value:
            cls.write(users, {
                'avatar_filename': None,
            })
            return

        db_name = Transaction().cursor.dbname
        user_dir = os.path.join(config.get('database', 'path'), db_name,
                                'user', 'avatar')

        if not value:
            cls.write(users, {
                'avatar_filename': None,
            })
            return

        for user in users:
            file_name = user['avatar_filename']

            file_mime, _ = guess_type(file_name)
            if not file_mime:
                cls.raise_user_error('not_file_mime', {
                    'file_name': file_name,
                })
            if file_mime not in IMAGE_TYPES:
                cls.raise_user_error('not_file_mime_image', {
                    'file_name': file_name,
                })

            _, ext = file_mime.split('/')
            digest = '%s.%s' % (hashlib.md5(value).hexdigest(), ext)
            subdir1 = digest[0:2]
            subdir2 = digest[2:4]
            directory = os.path.join(user_dir, subdir1, subdir2)
            filename = os.path.join(directory, digest)

            if not os.path.isdir(directory):
                os.makedirs(directory, 0775)
            os.umask(0022)
            with open(filename, 'wb') as file_p:
                file_p.write(value)

            # square and thumbnail thumb image
            thumb_size = AVATAR_SIZE, AVATAR_SIZE
            try:
                im = Image.open(filename)
            except:
                if os.path.exists(filename):
                    os.remove(filename)
                cls.raise_user_error('not_file_mime_image', {
                    'file_name': file_name,
                })

            width, height = im.size
            if width > height:
                delta = width - height
                left = int(delta / 2)
                upper = 0
                right = height + left
                lower = height
            else:
                delta = height - width
                left = 0
                upper = int(delta / 2)
                right = width
                lower = width + upper

            im = im.crop((left, upper, right, lower))
            im.thumbnail(thumb_size, Image.ANTIALIAS)
            im.save(filename)

            cls.write([user], {
                'avatar_filename': digest,
            })

    @staticmethod
    def order_code(tables):
        table, _ = tables[None]
        return [CharLength(table.code), table.code]

    @staticmethod
    def default_active():
        return True

    @staticmethod
    def default_categories():
        return Transaction().context.get('categories', [])

    @staticmethod
    def default_addresses():
        if Transaction().user == 0:
            return []
        Address = Pool().get('party.address')
        fields_names = list(x for x in Address._fields.keys()
                            if x not in ('id', 'create_uid', 'create_date',
                                         'write_uid', 'write_date'))
        return [Address.default_get(fields_names)]

    @staticmethod
    def default_lang():
        Configuration = Pool().get('party.configuration')
        config = Configuration(1)
        if config.party_lang:
            return config.party_lang.id

    @staticmethod
    def default_code_readonly():
        Configuration = Pool().get('party.configuration')
        config = Configuration(1)
        return bool(config.party_sequence)

    def get_code_readonly(self, name):
        return True

    @fields.depends('vat_country', 'vat_number')
    def on_change_with_vat_code(self, name=None):
        return (self.vat_country or '') + (self.vat_number or '')

    @classmethod
    def search_vat_code(cls, name, clause):
        res = []
        value = clause[2]
        for country, _ in VAT_COUNTRIES:
            if isinstance(value, basestring) \
                    and country \
                    and value.upper().startswith(country):
                res.append(('vat_country', '=', country))
                value = value[len(country):]
                break
        res.append(('vat_number', clause[1], value))
        return res

    def get_full_name(self, name):
        return self.name

    def get_mechanism(self, name):
        for mechanism in self.contact_mechanisms:
            if mechanism.type == name:
                return mechanism.value
        return ''

    @classmethod
    def create(cls, vlist):
        Sequence = Pool().get('ir.sequence')
        Configuration = Pool().get('party.configuration')

        vlist = [x.copy() for x in vlist]
        for values in vlist:
            if not values.get('code'):
                config = Configuration(1)
                values['code'] = Sequence.get_id(config.party_sequence.id)
            values.setdefault('addresses', None)
        return super(Party, cls).create(vlist)

    @classmethod
    def copy(cls, parties, default=None):
        if default is None:
            default = {}
        default = default.copy()
        default['code'] = None
        return super(Party, cls).copy(parties, default=default)

    @classmethod
    def search_global(cls, text):
        for id_, rec_name, icon in super(Party, cls).search_global(text):
            icon = icon or 'tryton-party'
            yield id_, rec_name, icon

    @classmethod
    def search_rec_name(cls, name, clause):
        return [
            'OR',
            ('code', ) + tuple(clause[1:]),
            ('name', ) + tuple(clause[1:]),
        ]

    def address_get(self, type=None):
        """
        Try to find an address for the given type, if no type matches
        the first address is returned.
        """
        Address = Pool().get("party.address")
        addresses = Address.search([("party", "=", self.id),
                                    ("active", "=", True)],
                                   order=[('sequence', 'ASC'), ('id', 'ASC')])
        if not addresses:
            return None
        default_address = addresses[0]
        if not type:
            return default_address
        for address in addresses:
            if getattr(address, type):
                return address
        return default_address

    @classmethod
    def validate(cls, parties):
        super(Party, cls).validate(parties)
        for party in parties:
            party.check_vat()

    def check_vat(self):
        '''
        Check the VAT number depending of the country.
        http://sima-pc.com/nif.php
        '''
        if not HAS_VATNUMBER:
            return
        vat_number = self.vat_number

        if not self.vat_country:
            return

        if not getattr(vatnumber,
                       'check_vat_' + self.vat_country.lower())(vat_number):

            #Check if user doesn't have put country code in number
            if vat_number.startswith(self.vat_country):
                vat_number = vat_number[len(self.vat_country):]
                Party.write([self], {
                    'vat_number': vat_number,
                })
            else:
                self.raise_user_error('invalid_vat', {
                    'vat': vat_number,
                    'party': self.rec_name,
                })
Ejemplo n.º 13
0
class Service(Workflow, ModelSQL, ModelView):
    'Service'
    __name__ = 'service.service'
    __history = True
    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        readonly=True,
        select=True,
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=',
                      '!='), Eval('context', {}).get('company', -1)),
        ],
        depends=_DEPENDS)
    party = fields.Many2One('party.party',
                            'Party',
                            states=_STATES,
                            required=True)
    number_service = fields.Char('No. Comprobante', readonly=True)
    type = fields.Selection(_TYPE,
                            'Type',
                            select=True,
                            states={
                                'readonly':
                                ((Eval('state') == 'delivered')
                                 | Eval('context', {}).get('type')),
                            })

    total = fields.Function(
        fields.Numeric('Total',
                       states={
                           'invisible': Eval('type') == 'home_service',
                       }), 'get_amount')

    entry_date = fields.Date('Entry Date',
                             states=_STATES,
                             domain=[('entry_date', '<',
                                      Eval('delivery_date', None))],
                             depends=['delivery_date'])
    delivery_date = fields.Date('Estimated Delivery Date',
                                states=_STATES,
                                domain=[('delivery_date', '>',
                                         Eval('entry_date', None))],
                                depends=['entry_date'])
    technical = fields.Many2One('company.employee',
                                'Technical',
                                states=_STATES)
    garanty = fields.Boolean('Garanty', help="Income Garanty", states=_STATES)
    new = fields.Boolean('New',
                         states={
                             'invisible': ~Eval('garanty', True),
                             'readonly': Eval('state') == 'delivered',
                         })
    lined = fields.Boolean('Lined',
                           states={
                               'invisible': ~Eval('garanty', True),
                               'readonly': Eval('state') == 'delivered',
                           })
    beaten = fields.Boolean('Beaten',
                            states={
                                'invisible': ~Eval('garanty', True),
                                'readonly': Eval('state') == 'delivered',
                            })
    broken = fields.Boolean('Broken',
                            states={
                                'invisible': ~Eval('garanty', True),
                                'readonly': Eval('state') == 'delivered',
                            })
    stained = fields.Boolean('Stained',
                             states={
                                 'invisible': ~Eval('garanty', True),
                                 'readonly': Eval('state') == 'delivered',
                             })
    invoice_date = fields.Date('Invoice Date',
                               states={
                                   'invisible': ~Eval('garanty', True),
                                   'readonly': Eval('state') == 'delivered',
                               })
    invoice_number = fields.Char('Invoice number',
                                 states={
                                     'invisible': ~Eval('garanty', True),
                                     'readonly': Eval('state') == 'delivered',
                                 })
    case_number = fields.Char('Case number',
                              states={
                                  'invisible': ~Eval('garanty', True),
                                  'readonly': Eval('state') == 'delivered',
                              })
    send_date = fields.Date('Send Date',
                            states={
                                'invisible': ~Eval('garanty', True),
                                'readonly': Eval('state') == 'delivered',
                            })
    remission = fields.Char('No. guide remission',
                            states={
                                'invisible': ~Eval('garanty', True),
                                'readonly': Eval('state') == 'delivered',
                            })
    transport = fields.Char('Transport',
                            states={
                                'invisible': ~Eval('garanty', True),
                                'readonly': Eval('state') == 'delivered',
                            })
    photo = fields.Binary('Foto', states=_STATES)
    state = fields.Selection([('pending', 'Pending'), ('review', 'In Review'),
                              ('ready', 'Ready'),
                              ('without', 'Without Solution'),
                              ('warranty', 'Warranty not cover'),
                              ('delivered', 'Delivered')],
                             'State',
                             readonly=True)
    lines = fields.One2Many('service.service.line',
                            'service',
                            'Lines',
                            states=_STATES)

    accessories = fields.Text('Accessories',
                              states={
                                  'readonly': Eval('accessories') != '',
                              })
    observations = fields.Text('Observations', states=_STATES)
    history_lines = fields.One2Many('service.service.history_lines', 'service',
                                    'Lines')
    total_home_service = fields.Numeric('Total',
                                        states={
                                            'invisible':
                                            Eval('type') == 'service',
                                        })

    detail = fields.Text('Repair Detail',
                         states={
                             'invisible': Eval('state') != 'delivered',
                             'readonly': Eval('detail') != '',
                         })
    state_date = fields.Function(fields.Char('State Date'), 'get_state_date')

    equipo = fields.Char('Equipo')
    marca = fields.Char('Marca')
    modelo = fields.Char('Modelo')

    direccion = fields.Char(u'Dirección',
                            states={
                                'invisible': ~Eval('party'),
                            })
    telefono = fields.Char(u'Teléfono', states={
        'invisible': ~Eval('party'),
    })
    correo = fields.Char(u'Correo Electrónico',
                         states={
                             'invisible': ~Eval('party'),
                         })

    @classmethod
    def __setup__(cls):
        super(Service, cls).__setup__()
        cls.__rpc__['getTechnicalService'] = RPC(check_access=False,
                                                 readonly=False)

        cls._error_messages.update({
            'modify_invoice': ('You can not modify service "%s".'),
            'delete_cancel': ('You can not delete service "%s".'),
        })

        cls._transitions |= set((
            ('pending', 'review'),
            ('review', 'ready'),
            ('review', 'without'),
            ('review', 'warranty'),
            ('ready', 'delivered'),
            ('without', 'delivered'),
            ('warranty', 'delivered'),
        ))

        cls._buttons.update({
            'review': {
                'invisible': Eval('state') != ('pending')
            },
            'ready': {
                'invisible':
                Eval('state').in_(['pending', 'ready', 'without', 'delivered'])
            },
            'without': {
                'invisible':
                Eval('state').in_(['pending', 'ready', 'without', 'delivered'])
            },
            'warranty': {
                'invisible':
                ~Eval('garanty', True) | (Eval('state').in_(
                    ['pending', 'ready', 'without', 'delivered']))
            },
            'delivered': {
                'invisible':
                Eval('state').in_(['review', 'pending', 'delivered'])
            },
        })

    @fields.depends('invoice_date', 'garanty')
    def on_change_invoice_date(self):
        res = {}
        Date = Pool().get('ir.date')
        if self.garanty != None:
            year = Date.today() - datetime.timedelta(days=365)
            if self.invoice_date < year:
                res['invoice_date'] = self.invoice_date
                self.raise_user_error(
                    u'Está seguro de la fecha de ingreso: "%s"'
                    u'tiene mas de un año de garantia', (self.invoice_date))
        else:
            res['invoice_date'] = self.invoice_date

        return res

    @fields.depends('party')
    def on_change_party(self):
        res = {}
        if self.party:
            if self.party.addresses[0]:
                res['direccion'] = self.party.addresses[0].street
            else:
                res['direccion'] = ""
            if self.party.phone:
                res['telefono'] = self.party.phone
            elif self.party.mobile:
                res['telefono'] = self.party.mobile
            else:
                res['telefono'] = ""
            if self.party.email:
                res['correo'] = self.party.email
            else:
                res['correo'] = ""
        else:
            res['direccion'] = ""
            res['telefono'] = ""
            res['correo'] = ""

        return res

    @fields.depends('lines')
    def on_change_lines(self):
        res = {}
        if self.lines:
            for line in self.lines:
                if line.product:
                    res['equipo'] = line.periferic.name
                else:
                    res['equipo'] = ""

                if line.trademark:
                    res['marca'] = line.trademark.name
                else:
                    res['marca'] = ""

                if line.model:
                    res['modelo'] = line.model
                else:
                    res['modelo'] = ""
        return res

    @classmethod
    def get_state_date(cls, services, names):
        pool = Pool()
        Date = pool.get('ir.date')
        date_now = Date.today()
        result = {n: {s.id: '' for s in services} for n in names}
        for name in names:
            for service in services:
                if (service.delivery_date < date_now) and (service.state !=
                                                           'delivered'):
                    result[name][service.id] = 'vencida'
                elif (service.delivery_date
                      == date_now) and (service.state != 'delivered'):
                    result[name][service.id] = 'vence_hoy'
                else:
                    result[name][service.id] = ''
        return result

    @staticmethod
    def default_entry_date():
        Date = Pool().get('ir.date')
        return Date.today()

    @staticmethod
    def default_accessories():
        return ''

    @staticmethod
    def default_detail():
        return ''

    @staticmethod
    def default_delivery_date():
        Date = Pool().get('ir.date')
        return Date.today() + datetime.timedelta(days=1)

    @staticmethod
    def default_state():
        return 'pending'

    @staticmethod
    def default_type():
        return Transaction().context.get('type', 'service')

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @classmethod
    def get_amount(cls, services, names):
        amount = Decimal(0.0)
        total = dict((i.id, _ZERO) for i in services)
        for service in services:
            for line in service.lines:
                if line.reference_amount:
                    amount += line.reference_amount
            total[service.id] = amount

        result = {
            'total': total,
        }
        for key in result.keys():
            if key not in names:
                del result[key]
        return result

    @fields.depends('total', 'total_home_service')
    def on_change_total_home_service(self):
        res = {}

        if self.total_home_service:
            res['total'] = self.total_home_service
        else:
            res['total'] = Decimal(0.0)

        return res

    def set_number(self):
        pool = Pool()
        Period = pool.get('account.period')
        Sequence = pool.get('ir.sequence.strict')
        Date = pool.get('ir.date')

        if self.number_service:
            return

        test_state = True

        accounting_date = self.entry_date
        period_id = Period.find(self.company.id,
                                date=accounting_date,
                                test_state=test_state)
        period = Period(period_id)
        sequence = period.get_service_sequence(self.type)
        if not sequence:
            self.raise_user_error('no_withholding_sequence', {
                'withholding': self.rec_name,
                'period': period.rec_name,
            })
        with Transaction().set_context(date=self.entry_date or Date.today()):
            number = Sequence.get_id(sequence.id)
            vals = {'number_service': number}
            if (not self.entry_date and self.type in ('service')):
                vals['entry_date'] = Transaction().context['date']
        self.write([self], vals)

    @classmethod
    def check_modify(cls, services):
        for service in services:
            if (service.state in ('delivered')):
                cls.raise_user_error('modify_invoice',
                                     (service.number_service, ))

    @classmethod
    def delete(cls, services):
        cls.check_modify(services)
        for service in services:
            if (service.state in ('review', 'ready', 'without', 'warranty',
                                  'delivered')):
                cls.raise_user_error('delete_cancel',
                                     (service.number_service, ))
        super(Service, cls).delete(services)

    @classmethod
    def copy(cls, services, default=None):
        if default is None:
            default = {}
        default = default.copy()
        default['state'] = 'pending'
        default['number_service'] = None
        default['type'] = 'service'
        return super(Service, cls).copy(services, default=default)

    @classmethod
    @ModelView.button
    @Workflow.transition('review')
    def review(cls, services):
        for service in services:
            pool = Pool()
            Contact = pool.get('party.contact_mechanism')
            Address = pool.get('party.address')

            if service.party:
                if service.party.addresses[0].street:
                    addresses = service.party.addresses[0]
                    addresses.street = service.direccion
                    addresses.save()
                else:
                    if service.direccion:
                        party = service.party
                        party.address = Address.create([{
                            'street':
                            service.direccion,
                            'party':
                            service.party.id
                        }])
                        party.save()
                    else:
                        service.raise_user_error(
                            u'Actualice la dirección del cliente')

                if service.party.email:
                    emails = Contact.search([('party', '=', service.party),
                                             ('type', '=', 'email')])
                    for email in emails:
                        email.value = service.correo
                        email.save()
                else:
                    if service.correo:
                        contact_mechanisms = []
                        contact_mechanisms.append({
                            'type': 'email',
                            'value': service.correo,
                            'party': service.party.id
                        })
                        contact_mechanisms = Contact.create(contact_mechanisms)
                    else:
                        service.raise_user_error(
                            'Actualice el correo del cliente')

                if service.party.phone:
                    phones = Contact.search([('party', '=', service.party),
                                             ('type', '=', 'phone')])
                    for phone in phones:
                        phone.value = service.telefono
                        phone.save()
                else:
                    if service.telefono:
                        contact_mechanisms = []
                        contact_mechanisms.append({
                            'type': 'phone',
                            'value': service.telefono,
                            'party': service.party.id,
                        })
                        contact_mechanisms = Contact.create(contact_mechanisms)
                    else:
                        service.raise_user_error(
                            u'Actualice el teléfono del cliente')

            service.raise_user_warning(
                'datos%s' % service.id,
                u'Está seguro que los datos del cliente:\n "%s"'
                u'\nCorreo: "%s" y Teléfono: "%s", \nestán actualizados.',
                (service.party.name, service.correo, service.telefono))

            service.set_number()

        cls.write([i for i in services if i.state != 'review'], {
            'state': 'review',
        })

    @classmethod
    @ModelView.button
    @Workflow.transition('ready')
    def ready(cls, services):
        cls.write([i for i in services if i.state != 'ready'], {
            'state': 'ready',
        })

    @classmethod
    @ModelView.button
    @Workflow.transition('without')
    def without(cls, services):
        cls.write([i for i in services if i.state != 'without'], {
            'state': 'without',
        })

    @classmethod
    @ModelView.button
    @Workflow.transition('warranty')
    def warranty(cls, services):
        cls.write([i for i in services if i.state != 'warranty'], {
            'state': 'warranty',
        })

    @classmethod
    @ModelView.button
    @Workflow.transition('delivered')
    def delivered(cls, services):
        cls.write([i for i in services if i.state != 'delivered'], {
            'state': 'delivered',
        })

    @classmethod
    def getTechnicalService(cls, identificacion):
        pool = Pool()
        Service = pool.get('service.service')
        Party = pool.get('party.party')
        parties = Party.search([('vat_number', '=', identificacion)])
        for p in parties:
            party = p
        services = Service.search([('party', '=', party)])
        all_services = []
        if services:
            for service in services:
                lines_services = {}
                for line in service.lines:
                    lines_services[0] = str(service.entry_date)
                    lines_services[1] = str(service.delivery_date)
                    lines_services[2] = service.number_service
                    lines_services[3] = line.periferic.name
                    lines_services[4] = line.trademark.name
                    lines_services[5] = line.model
                    lines_services[6] = line.failure
                    lines_services[7] = str(line.reference_amount)
                    lines_services[8] = line.technical.party.name
                    lines_services[9] = service.state
                    lines_services[10] = service.accessories
                    lines_services[11] = service.detail
                    all_services.append(lines_services)
            return all_services
        else:
            return []
Ejemplo n.º 14
0
class Company(metaclass=PoolMeta):
    __name__ = 'company.company'

    pem_certificate = fields.Binary('PEM Certificate')
    encrypted_private_key = fields.Binary('Encrypted Private Key')
    private_key = fields.Function(fields.Binary('Private Key'),
                                  'get_private_key', 'set_private_key')

    @classmethod
    def get_private_key(cls, companies, name=None):
        converter = bytes
        default = None
        format_ = Transaction().context.pop('%s.%s' % (cls.__name__, name), '')
        if format_ == 'size':
            converter = len
            default = 0

        pkeys = []
        for company in companies:
            key = company._get_private_key(name)
            if not key:
                continue
            pkeys.append(key)

        if not pkeys:
            return {company.id: None for x in companies}

        return {
            company.id: converter(pkey) if pkey else default
            for (company, pkey) in zip(companies, pkeys)
        }

    def _get_private_key(self, name=None):
        if not self.encrypted_private_key:
            return None
        fernet = self.get_fernet_key()
        if not fernet:
            return None
        decrypted_key = fernet.decrypt(bytes(self.encrypted_private_key))
        return decrypted_key

    @classmethod
    def set_private_key(cls, companies, name, value):
        encrypted_key = None
        if value:
            fernet = cls.get_fernet_key()
            encrypted_key = fernet.encrypt(bytes(value))
        cls.write(companies, {'encrypted_private_key': encrypted_key})

    @classmethod
    def get_fernet_key(cls):
        fernet_key = config.get('cryptography', 'fernet_key')
        if not fernet_key:
            _logger.error('Missing Fernet key configuration')
            # raise UserError(gettext('aeat_sii.msg_missing_fernet_key'))
        else:
            return Fernet(fernet_key)

    @contextmanager
    def tmp_ssl_credentials(self):
        if not self.pem_certificate or not self.private_key:
            raise UserError(gettext('aeat_sii.msg_missing_pem_cert'))
        with NamedTemporaryFile(suffix='.crt') as crt:
            with NamedTemporaryFile(suffix='.pem') as key:
                crt.write(self.pem_certificate)
                key.write(self.private_key)
                crt.flush()
                key.flush()
                yield (crt.name, key.name)
Ejemplo n.º 15
0
class Party:
    __name__ = 'party.party'
    uuid = fields.Char('UUID',
                       required=True,
                       help='Universally Unique Identifier')
    vcard = fields.Binary('VCard')

    @classmethod
    def __setup__(cls):
        super(Party, cls).__setup__()
        t = cls.__table__()
        cls._sql_constraints += [
            ('uuid_uniq', Unique(t, t.uuid),
             'The UUID of the party must be unique.'),
        ]

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        cursor = Transaction().connection.cursor()
        table = TableHandler(cls, module_name)
        sql_table = cls.__table__()

        if not table.column_exist('uuid'):
            table.add_raw_column('uuid', cls.uuid.sql_type(),
                                 cls.uuid.sql_format, None, None)
            cursor.execute(*sql_table.select(sql_table.id))
            for id, in cursor.fetchall():
                cursor.execute(*sql_table.update(columns=[sql_table.uuid],
                                                 values=[cls.default_uuid()],
                                                 where=sql_table.id == id))
        super(Party, cls).__register__(module_name)

    @staticmethod
    def default_uuid():
        return str(uuid.uuid4())

    @classmethod
    def create(cls, vlist):
        Collection = Pool().get('webdav.collection')

        parties = super(Party, cls).create(vlist)
        # Restart the cache for vcard
        Collection._vcard_cache.clear()
        return parties

    @classmethod
    def copy(cls, parties, default=None):
        if default is None:
            default = {}
        new_parties = []
        for party in parties:
            current_default = default.copy()
            current_default['uuid'] = cls.default_uuid()
            new_party, = super(Party, cls).copy([party],
                                                default=current_default)
            new_parties.append(new_party)
        return new_parties

    @classmethod
    def write(cls, parties, values, *args):
        Collection = Pool().get('webdav.collection')

        super(Party, cls).write(parties, values, *args)
        # Restart the cache for vcard
        Collection._vcard_cache.clear()

    @classmethod
    def delete(cls, parties):
        Collection = Pool().get('webdav.collection')

        super(Party, cls).delete(parties)
        # Restart the cache for vcard
        Collection._vcard_cache.clear()

    def vcard2values(self, vcard):
        '''
        Convert vcard to values for create or write
        '''
        Address = Pool().get('party.address')

        res = {}
        res['name'] = vcard.fn.value
        if not hasattr(vcard, 'n'):
            vcard.add('n')
            vcard.n.value = vobject.vcard.Name(vcard.fn.value)
        res['vcard'] = vcard.serialize()
        if not self.id:
            if hasattr(vcard, 'uid'):
                res['uuid'] = vcard.uid.value
            res['addresses'] = []
            to_create = []
            for adr in vcard.contents.get('adr', []):
                vals = Address.vcard2values(adr)
                to_create.append(vals)
            if to_create:
                res['addresses'].append(('create', to_create))
            res['contact_mechanisms'] = []
            to_create = []
            for email in vcard.contents.get('email', []):
                vals = {}
                vals['type'] = 'email'
                vals['value'] = email.value
                to_create.append(vals)
            if to_create:
                res['contact_mechanisms'].append(('create', to_create))
            to_create = []
            for tel in vcard.contents.get('tel', []):
                vals = {}
                vals['type'] = 'phone'
                if hasattr(tel, 'type_param') \
                        and 'cell' in tel.type_param.lower():
                    vals['type'] = 'mobile'
                vals['value'] = tel.value
                to_create.append(vals)
            if to_create:
                res['contact_mechanisms'].append(('create', to_create))
        else:
            i = 0
            res['addresses'] = []
            addresses_todelete = []
            for address in self.addresses:
                try:
                    adr = vcard.contents.get('adr', [])[i]
                except IndexError:
                    addresses_todelete.append(address.id)
                    i += 1
                    continue
                if not hasattr(adr, 'value'):
                    addresses_todelete.append(address.id)
                    i += 1
                    continue
                vals = Address.vcard2values(adr)
                res['addresses'].append(('write', [address.id], vals))
                i += 1
            if addresses_todelete:
                res['addresses'].append(('delete', addresses_todelete))
            try:
                new_addresses = vcard.contents.get('adr', [])[i:]
            except IndexError:
                new_addresses = []
            to_create = []
            for adr in new_addresses:
                if not hasattr(adr, 'value'):
                    continue
                vals = Address.vcard2values(adr)
                to_create.append(vals)
            if to_create:
                res['addresses'].append(('create', to_create))

            i = 0
            res['contact_mechanisms'] = []
            contact_mechanisms_todelete = []
            for cm in self.contact_mechanisms:
                if cm.type != 'email':
                    continue
                try:
                    email = vcard.contents.get('email', [])[i]
                except IndexError:
                    contact_mechanisms_todelete.append(cm.id)
                    i += 1
                    continue
                vals = {}
                vals['value'] = email.value
                res['contact_mechanisms'].append(('write', cm.id, vals))
                i += 1
            try:
                new_emails = vcard.contents.get('email', [])[i:]
            except IndexError:
                new_emails = []
            to_create = []
            for email in new_emails:
                if not hasattr(email, 'value'):
                    continue
                vals = {}
                vals['type'] = 'email'
                vals['value'] = email.value
                to_create.append(vals)
            if to_create:
                res['contact_mechanisms'].append(('create', to_create))

            i = 0
            for cm in self.contact_mechanisms:
                if cm.type not in ('phone', 'mobile'):
                    continue
                try:
                    tel = vcard.contents.get('tel', [])[i]
                except IndexError:
                    contact_mechanisms_todelete.append(cm.id)
                    i += 1
                    continue
                vals = {}
                vals['value'] = tel.value
                res['contact_mechanisms'].append(('write', cm.id, vals))
                i += 1
            try:
                new_tels = vcard.contents.get('tel', [])[i:]
            except IndexError:
                new_tels = []
            to_create = []
            for tel in new_tels:
                if not hasattr(tel, 'value'):
                    continue
                vals = {}
                vals['type'] = 'phone'
                if hasattr(tel, 'type_param') \
                        and 'cell' in tel.type_param.lower():
                    vals['type'] = 'mobile'
                vals['value'] = tel.value
                to_create.append(vals)
            if to_create:
                res['contact_mechanisms'].append(('create', to_create))

            if contact_mechanisms_todelete:
                res['contact_mechanisms'].append(
                    ('delete', contact_mechanisms_todelete))
        return res
Ejemplo n.º 16
0
class BinaryRequired(ModelSQL):
    'Binary Required'
    __name__ = 'test.binary_required'
    binary = fields.Binary('Binary Required', required=True)
class Invoice:
    'Invoice'
    __name__ = 'account.invoice'

    pos = fields.Many2One('account.pos',
                          'Point of Sale',
                          on_change=['pos', 'party', 'type', 'company'],
                          states=_POS_STATES,
                          depends=_DEPENDS)
    invoice_type = fields.Many2One('account.pos.sequence',
                                   'Invoice Type',
                                   domain=([('pos', '=', Eval('pos'))]),
                                   states=_POS_STATES,
                                   depends=_DEPENDS)

    pyafipws_concept = fields.Selection(
        [
            ('1', u'1-Productos'),
            ('2', u'2-Servicios'),
            ('3', u'3-Productos y Servicios (mercado interno)'),
            ('4', u'4-Otros (exportación)'),
            ('', ''),
        ],
        'Concepto',
        select=True,
        states={
            'readonly': Eval('state') != 'draft',
            'required': Eval('pos.pos_type') == 'electronic',
        },
        depends=['state'])
    pyafipws_billing_start_date = fields.Date(
        'Fecha Desde',
        states=_BILLING_STATES,
        depends=_DEPENDS,
        help=u"Seleccionar fecha de fin de servicios - Sólo servicios")
    pyafipws_billing_end_date = fields.Date(
        'Fecha Hasta',
        states=_BILLING_STATES,
        depends=_DEPENDS,
        help=u"Seleccionar fecha de inicio de servicios - Sólo servicios")
    pyafipws_cae = fields.Char(
        'CAE',
        size=14,
        readonly=True,
        help=u"Código de Autorización Electrónico, devuelto por AFIP")
    pyafipws_cae_due_date = fields.Date(
        'Vencimiento CAE',
        readonly=True,
        help=u"Fecha tope para verificar CAE, devuelto por AFIP")
    pyafipws_barcode = fields.Char(
        u'Codigo de Barras',
        size=40,
        help=u"Código de barras para usar en la impresión",
        readonly=True,
    )
    pyafipws_number = fields.Char(
        u'Número',
        size=13,
        readonly=True,
        help=u"Número de factura informado a la AFIP")

    transactions = fields.One2Many('account_invoice_ar.afip_transaction',
                                   'invoice',
                                   u"Transacciones",
                                   readonly=True)

    tipo_comprobante = fields.Selection(
        TIPO_COMPROBANTE,
        'Comprobante',
        select=True,
        depends=['state', 'type'],
        states={
            'invisible': Eval('type').in_(['out_invoice', 'out_credit_note']),
            'readonly': Eval('state') != 'draft',
            'required': Eval('type').in_(['in_invoice', 'in_credit_note']),
        })

    pyafipws_incoterms = fields.Selection(
        INCOTERMS,
        'Incoterms',
    )

    qr_imagen = fields.Binary(u'Código QR', states={
        'invisible': True,
    })
    qr_codigo = fields.Char(u'Información Código QR',
                            states={
                                'invisible': True,
                            })
    qr_texto_modificado = fields.Char(u'Texto codificado Código QR',
                                      states={
                                          'invisible': True,
                                      })

    pyafipws_cmp_asoc = fields.Many2Many(
        'account.invoice-cmp.asoc',
        'invoice',
        'cmp_asoc',
        'Comprobantes asociados',
        domain=[
            ('company', '=', Eval('company', -1)),
            ('type', '=', 'out_invoice'),
            [
                'OR',
                ('state', 'in', ['posted', 'paid']),
                ('id', 'in', Eval('pyafipws_cmp_asoc')),
            ],
        ],
        states=_STATES,
        depends=_DEPENDS + ['company', 'pyafipws_cmp_asoc'])

    periodo_start_date = fields.Date(
        'Desde periodo asociado',
        states=_STATES,
        depends=_DEPENDS,
        help=u"Seleccionar fecha de fin del periodo - Sólo creditos y debitos")
    periodo_end_date = fields.Date(
        'Hasta periodo asociado',
        states=_STATES,
        depends=_DEPENDS,
        help=
        u"Seleccionar fecha de inicio del periodo - Sólo creditos y debitos")

    @classmethod
    def default_invoice_type(cls):
        return None

    @classmethod
    def __setup__(cls):
        super(Invoice, cls).__setup__()

        cls._buttons.update({
            'afip_post': {
                'invisible': ~Eval('state').in_(['draft', 'validated']),
            },
        })
        cls._error_messages.update({
         'missing_pyafipws_billing_date':
          u'Debe establecer los valores "Fecha desde" y "Fecha hasta" ' \
          u'en el Diario, correspondientes al servicio que se está facturando',
         'invalid_invoice_number':
          u'El número de la factura (%d), no coincide con el que espera ' \
          u'la AFIP (%d). Modifique la secuencia del diario',
         'not_cae':
          u'No fue posible obtener el CAE. Revise las Transacciones ' \
          u'para mas información',
         'invalid_journal':
          u'Este diario (%s) no tiene establecido los datos necesaios para ' \
          u'facturar electrónicamente',
         'missing_sequence':
          u'No existe una secuencia para facturas del tipo: %s',
         'too_many_sequences':
          u'Existe mas de una secuencia para facturas del tipo: %s',
         'missing_company_iva_condition': ('The iva condition on company '
           '"%(company)s" is missing.'),
         'missing_party_iva_condition': ('The iva condition on party '
           '"%(party)s" is missing.'),
         'not_invoice_type':
          u'El campo «Tipo de factura» en «Factura» es requerido.',
         'change_sale_configuration':
          u'Se debe cambiar la configuracion de la venta para procesar la factura de forma Manual.',
         'missing_pyafipws_incoterms':
          u'Debe establecer el valor de Incoterms si desea realizar un tipo de "Factura E".',
         })

    @classmethod
    @ModelView.button
    @Workflow.transition('validated')
    def validate_invoice(cls, invoices):
        for invoice in invoices:
            if invoice.type in ('out_invoice', 'out_credit_note'):
                invoice.check_invoice_type()
        super(Invoice, cls).validate(invoices)

    @classmethod
    def validate(cls, invoices):
        super(Invoice, cls).validate(invoices)
        for invoice in invoices:
            invoice.check_invoice_type()

    def check_invoice_type(self):
        if not self.company.party.iva_condition:
            self.raise_user_error('missing_company_iva_condition', {
                'company': self.company.rec_name,
            })
        if not self.party.iva_condition:
            self.raise_user_error('missing_party_iva_condition', {
                'party': self.party.rec_name,
            })
        if not self.invoice_type:
            if self.sales:
                self.raise_user_error('change_sale_configuration')
            else:
                if self.type in ('out_invoice', 'out_credit_note'):
                    self.raise_user_error('not_invoice_type')

    def on_change_pos(self):
        PosSequence = Pool().get('account.pos.sequence')

        if not self.pos:
            return {'invoice_type': None}

        res = {}
        client_iva = company_iva = None
        if self.party:
            client_iva = self.party.iva_condition
        if self.company:
            company_iva = self.company.party.iva_condition

        if company_iva == 'responsable_inscripto':
            if client_iva is None:
                return res
            if client_iva == 'responsable_inscripto':
                kind = 'A'
            elif client_iva == 'consumidor_final':
                kind = 'B'
            elif self.party.vat_country is None:
                self.raise_user_error('unknown_country')
            elif self.party.vat_country == u'AR':
                kind = 'B'
            else:
                kind = 'E'
        else:
            kind = 'C'

        invoice_type, invoice_type_desc = INVOICE_TYPE_AFIP_CODE[(self.type,
                                                                  kind)]
        sequences = PosSequence.search([('pos', '=', self.pos.id),
                                        ('invoice_type', '=', invoice_type)])
        if len(sequences) == 0:
            self.raise_user_error('missing_sequence', invoice_type_desc)
        elif len(sequences) > 1:
            self.raise_user_error('too_many_sequences', invoice_type_desc)
        else:
            res['invoice_type'] = sequences[0].id

        return res

    def set_number(self):
        super(Invoice, self).set_number()

        if self.type == 'out_invoice' or self.type == 'out_credit_note':
            vals = {}
            Sequence = Pool().get('ir.sequence')

            number = Sequence.get_id(self.invoice_type.invoice_sequence.id)
            vals['number'] = '%04d-%08d' % (self.pos.number, int(number))
            self.write([self], vals)

    def _get_move_line(self, date, amount):
        res = super(Invoice, self)._get_move_line(date, amount)

        if self.type[:3] == 'out':
            res['description'] = self.party.name + u' Nro. ' + self.number
        else:
            res['description'] = self.party.name + u' Nro. ' + self.reference

        if self.description:
            res['description'] += ' / ' + self.description

        return res

    @classmethod
    @ModelView.button
    @Workflow.transition('posted')
    def post(cls, invoices):
        Move = Pool().get('account.move')

        moves = []
        for invoice in invoices:
            if invoice.type == u'out_invoice' or invoice.type == u'out_credit_note':
                if not invoice.invoice_type:
                    invoice.raise_user_error('not_invoice_type')
                if invoice.pos:
                    if invoice.pos.pos_type == 'electronic':
                        invoice.do_pyafipws_request_cae()
                        if not invoice.pyafipws_cae:
                            invoice.raise_user_error('not_cae')
            invoice.set_number()
            if invoice.type == u'out_invoice' or invoice.type == u'out_credit_note':
                if invoice.pos.pos_type == 'electronic':
                    invoice.crear_codigo_qr()
            moves.append(invoice.create_move())
        cls.write(invoices, {
            'state': 'posted',
        })
        Move.post(moves)
        #Bug: https://github.com/tryton-ar/account_invoice_ar/issues/38
        #for invoice in invoices:
        #    if invoice.type in ('out_invoice', 'out_credit_note'):
        #        invoice.print_invoice()

    def do_pyafipws_request_cae(self):
        logger = logging.getLogger('pyafipws')
        "Request to AFIP the invoices' Authorization Electronic Code (CAE)"
        # if already authorized (electronic invoice with CAE), ignore
        if self.pyafipws_cae:
            logger.info(u'Se trata de obtener CAE de la factura que ya tiene. '\
               u'Factura: %s, CAE: %s', self.number, self.pyafipws_cae)
            return
        # get the electronic invoice type, point of sale and service:
        pool = Pool()

        Company = pool.get('company.company')
        company_id = Transaction().context.get('company')
        if not company_id:
            logger.info(u'No hay companía')
            return

        company = Company(company_id)

        tipo_cbte = self.invoice_type.invoice_type
        punto_vta = self.pos.number
        service = self.pos.pyafipws_electronic_invoice_service
        # check if it is an electronic invoice sale point:
        ##TODO
        #if not tipo_cbte:
        #    self.raise_user_error('invalid_sequence', pos.invoice_type.invoice_type)

        # authenticate against AFIP:
        auth_data = company.pyafipws_authenticate(service=service)

        # import the AFIP webservice helper for electronic invoice
        if service == 'wsfe':
            from pyafipws.wsfev1 import WSFEv1  # local market
            ws = WSFEv1()
            if company.pyafipws_mode_cert == 'homologacion':
                WSDL = "https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL"
            elif company.pyafipws_mode_cert == 'produccion':
                WSDL = "https://servicios1.afip.gov.ar/wsfev1/service.asmx?WSDL"
        #elif service == 'wsmtxca':
        #    from pyafipws.wsmtx import WSMTXCA, SoapFault   # local + detail
        #    ws = WSMTXCA()
        elif service == 'wsfex':
            from pyafipws.wsfexv1 import WSFEXv1  # foreign trade
            ws = WSFEXv1()
            if company.pyafipws_mode_cert == 'homologacion':
                WSDL = "https://wswhomo.afip.gov.ar/wsfexv1/service.asmx?WSDL"
            elif company.pyafipws_mode_cert == 'produccion':
                WSDL = "https://servicios1.afip.gov.ar/wsfexv1/service.asmx?WSDL"
        else:
            logger.critical(u'WS no soportado: %s', service)
            return

        # connect to the webservice and call to the test method
        ws.LanzarExcepciones = True
        ws.Conectar(wsdl=WSDL)
        # set AFIP webservice credentials:
        ws.Cuit = company.party.vat_number
        ws.Token = auth_data['token']
        ws.Sign = auth_data['sign']

        # get the last 8 digit of the invoice number
        if self.move:
            cbte_nro = int(self.move.number[-8:])
        else:
            Sequence = pool.get('ir.sequence')
            cbte_nro = int(
                Sequence(
                    self.invoice_type.invoice_sequence.id).get_number_next(''))

        # get the last invoice number registered in AFIP
        if service == "wsfe" or service == "wsmtxca":
            cbte_nro_afip = ws.CompUltimoAutorizado(tipo_cbte, punto_vta)

        elif service == 'wsfex':
            cbte_nro_afip = ws.GetLastCMP(tipo_cbte, punto_vta)
        cbte_nro_next = int(cbte_nro_afip or 0) + 1
        # verify that the invoice is the next one to be registered in AFIP

        if cbte_nro != cbte_nro_next:
            self.raise_user_error('invalid_invoice_number',
                                  (cbte_nro, cbte_nro_next))

        # invoice number range (from - to) and date:
        cbte_nro = cbt_desde = cbt_hasta = cbte_nro_next

        if self.invoice_date:
            fecha_cbte = self.invoice_date.strftime("%Y-%m-%d")
        else:
            Date = pool.get('ir.date')
            fecha_cbte = Date.today().strftime("%Y-%m-%d")

        if service != 'wsmtxca':
            fecha_cbte = fecha_cbte.replace("-", "")

        # due and billing dates only for concept "services"
        concepto = tipo_expo = int(self.pyafipws_concept or 0)
        if int(concepto) != 1:

            payments = self.payment_term.compute(self.total_amount,
                                                 self.currency)
            last_payment = max(payments, key=lambda x: x[0])[0]
            fecha_venc_pago = last_payment.strftime("%Y-%m-%d")
            if service != 'wsmtxca':
                fecha_venc_pago = fecha_venc_pago.replace("-", "")
            if self.pyafipws_billing_start_date:
                fecha_serv_desde = self.pyafipws_billing_start_date.strftime(
                    "%Y-%m-%d")
                if service != 'wsmtxca':
                    fecha_serv_desde = fecha_serv_desde.replace("-", "")
            else:
                fecha_serv_desde = None
            if self.pyafipws_billing_end_date:
                fecha_serv_hasta = self.pyafipws_billing_end_date.strftime(
                    "%Y-%m-%d")
                if service != 'wsmtxca':
                    fecha_serv_hasta = fecha_serv_hasta.replace("-", "")
            else:
                fecha_serv_hasta = None
        else:
            fecha_venc_pago = fecha_serv_desde = fecha_serv_hasta = None

        # customer tax number:
        if self.party.vat_number:
            nro_doc = self.party.vat_number
            if len(nro_doc) < 11:
                tipo_doc = 96  # DNI
            else:
                tipo_doc = 80  # CUIT
        else:
            nro_doc = "0"  # only "consumidor final"
            tipo_doc = 99  # consumidor final

        # invoice amount totals:
        imp_total = str("%.2f" % abs(self.total_amount))
        imp_tot_conc = "0.00"
        imp_neto = str("%.2f" % abs(self.untaxed_amount))
        imp_iva = str("%.2f" % abs(self.tax_amount))
        imp_subtotal = imp_neto  # TODO: not allways the case!
        imp_trib = "0.00"
        imp_op_ex = "0.00"
        if self.currency.code == 'ARS':
            moneda_id = "PES"
            moneda_ctz = 1
        else:
            moneda_id = {'USD': 'DOL'}[self.currency.code]
            ctz = 1 / self.currency.rate
            moneda_ctz = str("%.2f" % ctz)

        # foreign trade data: export permit, country code, etc.:
        if self.pyafipws_incoterms:
            incoterms = self.pyafipws_incoterms
            incoterms_ds = dict(self._fields['pyafipws_incoterms'].selection)[
                self.pyafipws_incoterms]
        else:
            incoterms = incoterms_ds = None

        if incoterms == None and incoterms_ds == None and service == 'wsfex':
            self.raise_user_error('missing_pyafipws_incoterms')

        if int(tipo_cbte) == 19 and tipo_expo == 1:
            permiso_existente = "N" or "S"  # not used now
        else:
            permiso_existente = ""
        obs_generales = self.comment
        if self.payment_term:
            forma_pago = self.payment_term.name
            obs_comerciales = self.payment_term.name
        else:
            forma_pago = obs_comerciales = None
        idioma_cbte = 1  # invoice language: spanish / español

        # customer data (foreign trade):
        nombre_cliente = self.party.name
        if self.party.vat_number:
            if self.party.vat_country == "AR":
                # use the Argentina AFIP's global CUIT for the country:
                cuit_pais_cliente = self.party.vat_number
                id_impositivo = None
            else:
                # use the VAT number directly
                id_impositivo = self.party.vat_number
                # TODO: the prefix could be used to map the customer country
                cuit_pais_cliente = None
        else:
            cuit_pais_cliente = id_impositivo = None
        if self.invoice_address:
            address = self.invoice_address
            domicilio_cliente = " - ".join([
                address.name or '',
                address.street or '',
                address.streetbis or '',
                address.zip or '',
                address.city or '',
            ])
        else:
            domicilio_cliente = ""
        if self.invoice_address.country:
            # map ISO country code to AFIP destination country code:
            pais_dst_cmp = {
                'ar': 200,
                'bo': 202,
                'br': 203,
                'ca': 204,
                'co': 205,
                'cu': 207,
                'cl': 208,
                'ec': 210,
                'us': 212,
                'mx': 218,
                'py': 221,
                'pe': 222,
                'uy': 225,
                've': 226,
                'cn': 310,
                'tw': 313,
                'in': 315,
                'il': 319,
                'jp': 320,
                'at': 405,
                'be': 406,
                'dk': 409,
                'es': 410,
                'fr': 412,
                'gr': 413,
                'it': 417,
                'nl': 423,
                'pt': 620,
                'uk': 426,
                'sz': 430,
                'de': 438,
                'ru': 444,
                'eu': 497,
                'cr': '206'
            }[self.invoice_address.country.code.lower()]

        # create the invoice internally in the helper
        if service == 'wsfe':
            ws.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta,
                            cbt_desde, cbt_hasta, imp_total, imp_tot_conc,
                            imp_neto, imp_iva, imp_trib, imp_op_ex, fecha_cbte,
                            fecha_venc_pago, fecha_serv_desde,
                            fecha_serv_hasta, moneda_id, moneda_ctz)
        elif service == 'wsmtxca':
            ws.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta,
                            cbt_desde, cbt_hasta, imp_total, imp_tot_conc,
                            imp_neto, imp_subtotal, imp_trib, imp_op_ex,
                            fecha_cbte, fecha_venc_pago, fecha_serv_desde,
                            fecha_serv_hasta, moneda_id, moneda_ctz,
                            obs_generales)
        elif service == 'wsfex':
            ws.CrearFactura(tipo_cbte, punto_vta, cbte_nro, fecha_cbte,
                            imp_total, tipo_expo, permiso_existente,
                            pais_dst_cmp, nombre_cliente, cuit_pais_cliente,
                            domicilio_cliente, id_impositivo, moneda_id,
                            moneda_ctz, obs_comerciales, obs_generales,
                            forma_pago, incoterms, idioma_cbte, incoterms_ds)

        # analyze VAT (IVA) and other taxes (tributo):
        if service in ('wsfe', 'wsmtxca'):
            for tax_line in self.taxes:
                tax = tax_line.tax
                if tax.group.name == "IVA":
                    iva_id = IVA_AFIP_CODE[tax.rate]
                    base_imp = ("%.2f" % abs(tax_line.base))
                    importe = ("%.2f" % abs(tax_line.amount))
                    # add the vat detail in the helper
                    ws.AgregarIva(iva_id, base_imp, importe)
                else:
                    if 'impuesto' in tax_line.tax.name.lower():
                        tributo_id = 1  # nacional
                    elif 'iibbb' in tax_line.tax.name.lower():
                        tributo_id = 3  # provincial
                    elif 'tasa' in tax_line.tax.name.lower():
                        tributo_id = 4  # municipal
                    else:
                        tributo_id = 99
                    desc = tax_line.name
                    base_imp = ("%.2f" % abs(tax_line.base))
                    importe = ("%.2f" % abs(tax_line.amount))
                    alic = "%.2f" % tax_line.base
                    # add the other tax detail in the helper
                    ws.AgregarTributo(tributo_id, desc, base_imp, alic,
                                      importe)

            if (self.invoice_type.invoice_type
                    in ('2', '3', '7', '8', '12', '13', '202', '203', '207',
                        '208', '212', '213')):

                if self.periodo_start_date and self.periodo_end_date:
                    periodo_asoc_desde = self.periodo_start_date.strftime(
                        "%Y%m%d")
                    periodo_asoc_hasta = self.periodo_end_date.strftime(
                        "%Y%m%d")
                    ws.AgregarPeriodoComprobantesAsociados(
                        fecha_desde=periodo_asoc_desde,
                        fecha_hasta=periodo_asoc_hasta)
                else:
                    if not self.pyafipws_cmp_asoc:
                        raise self.raise_user_error(
                            'Para debitos o creditos debe seleccionar el comprobante origen o el periodo asociado.'
                        )
                    for cbteasoc in self.pyafipws_cmp_asoc:
                        cbteasoc_tipo = int(cbteasoc.invoice_type.invoice_type)
                        if cbteasoc_tipo not in INVOICE_ASOC_AFIP_CODE[
                                self.invoice_type.invoice_type]:
                            raise self.raise_user_error(
                                'account_invoice_ar.msg_invalid_cmp_asoc')
                        cbteasoc_nro = int(cbteasoc.number[-8:])
                        cbteasoc_fecha_cbte = cbteasoc.invoice_date.strftime(
                            '%Y-%m-%d')
                        if service != 'wsmtxca':
                            cbteasoc_fecha_cbte = cbteasoc_fecha_cbte.replace(
                                '-', '')
                        ws.AgregarCmpAsoc(tipo=cbteasoc_tipo,
                                          pto_vta=punto_vta,
                                          nro=cbteasoc_nro,
                                          cuit=self.company.party.vat_number,
                                          fecha=cbteasoc_fecha_cbte)

                ## Agrego un item:
                #codigo = "PRO1"
                #ds = "Producto Tipo 1 Exportacion MERCOSUR ISO 9001"
                #qty = 2
                #precio = "150.00"
                #umed = 1 # Ver tabla de parámetros (unidades de medida)
                #bonif = "50.00"
                #imp_total = "250.00" # importe total final del artículo
        # analize line items - invoice detail
        # umeds
        # Parametros. Unidades de Medida, etc.
        # https://code.google.com/p/pyafipws/wiki/WSFEX#WSFEX/RECEX_Parameter_Tables
        if service in ('wsfex', 'wsmtxca'):
            for line in self.lines:
                if line.product:
                    codigo = line.product.code
                else:
                    codigo = 0
                ds = line.description
                qty = line.quantity
                umed = 7  # FIXME: (7 - unit)
                precio = str(line.unit_price)
                importe_total = str(line.amount)
                bonif = None  # line.discount
                #for tax in line.taxes:
                #    if tax.group.name == "IVA":
                #        iva_id = IVA_AFIP_CODE[tax.rate]
                #        imp_iva = importe * tax.rate
                #if service == 'wsmtxca':
                #    ws.AgregarItem(u_mtx, cod_mtx, codigo, ds, qty, umed,
                #            precio, bonif, iva_id, imp_iva, importe+imp_iva)
                if service == 'wsfex':
                    ws.AgregarItem(codigo, ds, qty, umed, precio,
                                   importe_total, bonif)

        # Request the authorization! (call the AFIP webservice method)
        try:
            if service == 'wsfe':
                ws.CAESolicitar()
                vto = ws.Vencimiento
            elif service == 'wsmtxca':
                ws.AutorizarComprobante()
                vto = ws.Vencimiento
            elif service == 'wsfex':
                ws.Authorize(self.id)
                vto = ws.FchVencCAE
        #except SoapFault as fault:
        #    msg = 'Falla SOAP %s: %s' % (fault.faultcode, fault.faultstring)
        except Exception, e:
            if ws.Excepcion:
                # get the exception already parsed by the helper
                #import ipdb; ipdb.set_trace()  # XXX BREAKPOINT
                msg = ws.Excepcion + ' ' + str(e)
            else:
                # avoid encoding problem when reporting exceptions to the user:
                import traceback
                import sys
                msg = traceback.format_exception_only(sys.exc_type,
                                                      sys.exc_value)[0]
        else:
Ejemplo n.º 18
0
class BinaryFileStorage(ModelSQL):
    "Binary in FileStorage"
    __name__ = 'test.binary_filestorage'
    binary = fields.Binary('Binary', file_id='binary_id')
    binary_id = fields.Char('Binary ID')
Ejemplo n.º 19
0
class ProductCategory:
    "Product Category extension for Nereid"
    __name__ = "product.category"

    uri = fields.Char(
        'URI', select=True,
        on_change_with=['name', 'uri', 'parent'], states=DEFAULT_STATE2
    )
    displayed_on_eshop = fields.Boolean('Displayed on E-Shop?')
    description = fields.Text('Description')
    image = fields.Many2One(
        'nereid.static.file', 'Image',
        states=DEFAULT_STATE
    )
    image_preview = fields.Function(
        fields.Binary('Image Preview'), 'get_image_preview'
    )
    sites = fields.Many2Many(
        'nereid.website-product.category',
        'category', 'website', 'Sites', states=DEFAULT_STATE
    )

    @classmethod
    def __setup__(cls):
        super(ProductCategory, cls).__setup__()
        cls._sql_constraints += [
            ('uri_uniq', 'UNIQUE(uri)', 'URI must be unique'),
        ]
        cls.per_page = 9

    @staticmethod
    def default_displayed_on_eshop():
        return True

    def get_image_preview(self, name=None):
        if self.image:
            return self.image.file_binary
        return None

    def on_change_with_uri(self):
        """Slugifies the full name of a category to
        make the uri on change of product name.
        Slugification will occur only if there is no uri filled from before.
        """
        if self.name and not self.uri:
            full_name = (self.parent and self.parent.rec_name or '') \
                + self.name
            return slugify(full_name)
        return self.uri

    @classmethod
    @ModelView.button
    def update_uri(cls, categories):
        """Update the uri of the category from the complete name.
        """
        for category in categories:
            cls.write([category], {'uri': slugify(category.rec_name)})

    @classmethod
    def render(cls, uri, page=1):
        """
        Renders the template 'category.jinja' with the category and the
        products of the category paginated in the context

        :param uri: URI of the product category
        :param page: Integer value of the page
        """
        ProductTemplate = Pool().get('product.template')

        categories = cls.search([
            ('displayed_on_eshop', '=', True),
            ('uri', '=', uri),
            ('sites', '=', request.nereid_website.id)
        ])
        if not categories:
            return NotFound('Product Category Not Found')

        # if only one category is found then it is rendered and
        # if more than one are found then the first one is rendered
        category = categories[0]
        products = Pagination(ProductTemplate, [
            ('products.displayed_on_eshop', '=', True),
            ('category', '=', category.id),
        ], page=page, per_page=cls.per_page)
        return render_template(
            'category.jinja', category=category, products=products
        )

    @classmethod
    def render_list(cls, page=1):
        """
        Renders the list of all categories which are displayed_on_shop=True
        paginated.

        :param page: Integer ID of the page
        """
        categories = Pagination(cls, [
            ('displayed_on_eshop', '=', True),
            ('sites', '=', request.nereid_website.id),
        ], page, cls.per_page)
        return render_template('category-list.jinja', categories=categories)

    @classmethod
    def get_categories(cls, page=1):
        """Return list of categories
        """
        return Pagination(cls, [
            ('displayed_on_eshop', '=', True),
            ('sites', '=', request.nereid_website.id)
        ], page, cls.per_page)

    @classmethod
    def get_root_categories(cls, page=1):
        """Return list of Root Categories."""
        return Pagination(cls, [
            ('displayed_on_eshop', '=', True),
            ('sites', '=', request.nereid_website.id),
            ('parent', '=', None),
        ], page, cls.per_page)

    @classmethod
    def context_processor(cls):
        """This function will be called by nereid to update
        the template context. Must return a dictionary that the context
        will be updated with.

        This function is registered with nereid.template.context_processor
        in xml code
        """
        return {
            'all_categories': cls.get_categories,
            'root_categories': cls.get_root_categories,
        }

    @classmethod
    def sitemap_index(cls):
        index = SitemapIndex(cls, [
            ('displayed_on_eshop', '=', True),
            ('id', 'in', request.nereid_website.get_categories())
        ])
        return index.render()

    @classmethod
    def sitemap(cls, page):
        sitemap_section = SitemapSection(
            cls, [
                ('displayed_on_eshop', '=', True),
                ('id', 'in', request.nereid_website.get_categories())
            ], page
        )
        sitemap_section.changefreq = 'daily'
        return sitemap_section.render()

    def get_absolute_url(self, **kwargs):
        return url_for(
            'product.category.render', uri=self.uri, **kwargs
        )

    def _json(self):
        """
        Return a JSON serializable dictionary of the category
        """
        return {
            'name': self.name,
            'id': self.id,
            'rec_name': self.rec_name,
        }
Ejemplo n.º 20
0
class ImportCSVFile(ModelSQL, ModelView):
    'Import CSV File'
    __name__ = 'import.csv.file'
    profile_csv = fields.Many2One(
        'import.csv',
        'Profile CSV',
        required=True,
        states={'readonly': (Eval('state') != 'draft')},
        depends=['state'])
    csv_file = fields.Binary('CSV File to import',
                             required=True,
                             filename='file_name',
                             states={'readonly': (Eval('state') != 'draft')},
                             depends=['state'])
    file_name = fields.Char('File Name',
                            required=True,
                            states={'readonly': (Eval('state') != 'draft')},
                            depends=['state'])
    skip_repeated = fields.Boolean(
        'Skip Repeated',
        states={'readonly': (Eval('state') != 'draft')},
        depends=['state'],
        help='If any record of the CSV file is already imported, skip it.')
    update_record = fields.Boolean(
        'Update Record',
        states={'readonly': (Eval('state') != 'draft')},
        depends=['state'],
        help=('If any record of the CSV file is found with the search domain, '
              'update the record.'))
    date_ = fields.DateTime('Date', required=True)
    state = fields.Selection([
        ('draft', 'Draft'),
        ('error', 'Error'),
        ('done', 'Done'),
    ],
                             'State',
                             states={
                                 'readonly': True,
                             })

    @classmethod
    def __setup__(cls):
        super(ImportCSVFile, cls).__setup__()
        cls._order.insert(0, ('date_', 'DESC'))
        cls._order.insert(1, ('id', 'DESC'))
        cls._error_messages.update({
            'csv_format_error':
            ('Please, check that the CSV file '
             'configuration matches with the format of the CSV file.'),
            'record_already_exists': ('Record %s skipped. '
                                      'Already exists.'),
            'record_added':
            'Record %s added.',
            'record_updated':
            'Record %s updated.',
            'email_subject':
            'CSV Import result',
            'user_email_error':
            '%s has not any email address',
            'import_successfully':
            'Successfully imported %s records.',
            'import_unsuccessfully':
            ('Unsuccessfully imported %s records.'
             'Check configuration profile or CSV file'),
        })
        cls._buttons.update({
            'import_file': {
                'invisible': (Eval('state') == 'done'),
            },
        })

    @classmethod
    def default_state(cls):
        return 'draft'

    @staticmethod
    def default_date_():
        return datetime.now()

    @classmethod
    def prepare_message(cls):
        User = Pool().get('res.user')

        user = User(Transaction().user)

        to_addr = user.email or config.get('email', 'from')
        if not to_addr:
            return
        return to_addr

    @classmethod
    def create_message(cls, from_addr, to_addrs, subject, message):
        msg = MIMEText(message, _charset='utf-8')
        msg['To'] = ', '.join(to_addrs)
        msg['From'] = from_addr
        msg['Subject'] = Header(subject, 'utf-8')
        return msg

    @classmethod
    def send_message(cls, message):
        to_addr = cls.prepare_message()
        if to_addr:
            from_addr = config.get('email', 'uri')
            subject = cls.raise_user_error('email_subject',
                                           raise_exception=False)
            msg = cls.create_message(from_addr, [to_addr], subject, message)
            sendmail(from_addr, [to_addr], msg)

    def read_csv_file(self):
        '''Read CSV data'''
        separator = self.profile_csv.separator
        if separator == "tab":
            separator = '\t'
        quote = self.profile_csv.quote

        file_ = self.csv_file
        # On python3 we must convert the binary file to string
        if hasattr(file_, 'decode'):
            file_ = file_.decode(self.profile_csv.character_encoding)
        data = StringIO(file_)
        if quote:
            rows = reader(data, delimiter=str(separator), quotechar=str(quote))
        else:
            rows = reader(data, delimiter=str(separator))
        return rows

    @classmethod
    def add_message_line(cls, csv_file, status, error_message, error_args):
        return ('%(time)s:\t%(profile)s '
                '(%(profile_id)s)\t%(filename)s\t%(status)s\t%(message)s') % {
                    'time':
                    datetime.now(),
                    'profile':
                    csv_file.profile_csv.rec_name,
                    'profile_id':
                    csv_file.profile_csv.id,
                    'filename':
                    csv_file.file_name,
                    'status':
                    status,
                    'message':
                    cls.raise_user_error(error_message,
                                         error_args=error_args,
                                         raise_exception=False),
                }

    @classmethod
    def import_file_default(cls, csv_file):
        '''Default Import CSV'''
        pool = Pool()

        profile_csv = csv_file.profile_csv
        model = profile_csv.model
        has_header = profile_csv.header
        skip_repeated = csv_file.skip_repeated
        update_record = csv_file.update_record

        Model = pool.get(model.model)

        data = csv_file.read_csv_file()
        if has_header:
            next(data, None)

        logs = []
        to_save = []
        for row in data:
            if not row:
                continue

            domain = []
            values = {}
            for column in profile_csv.columns:
                # each column, get value and assign in dict
                if column.constant:
                    value = column.constant
                else:
                    cells = column.column.split(',')
                    try:
                        vals = [row[int(c)] for c in cells if c]
                    except IndexError:
                        cls.raise_user_error('csv_format_error')
                    value = column.get_value(vals)

                if column.add_to_domain:
                    domain.append((column.field.name, '=', value))

                values[column.field.name] = value

            if domain:
                # search record exist
                records = Model().search(domain, limit=1)

                if skip_repeated and records:
                    logs.append(
                        cls.add_message_line(csv_file, 'skipped',
                                             'record_already_exists',
                                             (records[0].rec_name, )))
                    continue

                if update_record and records:
                    record, = records  # to update
                    logs.append(
                        cls.add_message_line(csv_file, 'done',
                                             'record_updated', (values)))
                else:
                    record = Model()  # to create
                    logs.append(
                        cls.add_message_line(csv_file, 'done', 'record_added',
                                             (values)))
            else:
                record = Model()  # to create
                logs.append(
                    cls.add_message_line(csv_file, 'done', 'record_added',
                                         (values)))

            # assign values to object record
            for k, v in values.iteritems():
                setattr(record, k, v)
            to_save.append(record)

        state = 'done'
        if to_save:
            try:
                Model.save(to_save)
                logs.insert(
                    0,
                    cls.add_message_line(csv_file, 'done',
                                         'import_successfully',
                                         (len(to_save), )))
            except:
                state = 'error'
                logs.insert(
                    0,
                    cls.add_message_line(csv_file, 'error',
                                         'import_unsuccessfully',
                                         (len(to_save), )))

        cls.write([csv_file], {'state': state})
        Transaction().connection.commit()  # force to commit

        if profile_csv.email:
            cls.send_message('\n'.join(logs))

    @classmethod
    def import_file_party(cls, csv_file):
        '''Party Import CSV'''
        pool = Pool()

        profile_csv = csv_file.profile_csv
        has_header = profile_csv.header
        skip_repeated = csv_file.skip_repeated
        update_record = csv_file.update_record

        Party = pool.get('party.party')
        Address = pool.get('party.address')
        ContactMechanism = pool.get('party.contact_mechanism')
        PartyIdentifier = pool.get('party.identifier')

        data = csv_file.read_csv_file()
        if has_header:
            next(data, None)

        # Create a new list with parent and child values for each record
        # Group CSV lines in same record. See party.csv example in test
        # rows = [{
        #    'record': {},
        #    'domain': [],
        #    }
        rows = []
        for row in data:
            is_party = True
            is_address = False
            is_contact = False

            identifiers = []
            domain = []
            values = {}
            for column in profile_csv.columns:
                # each column, get value and assign in dict
                cells = column.column.split(',')
                cell = int(cells[0])

                if column.constant:
                    value = column.constant
                else:
                    try:
                        vals = [row[int(c)] for c in cells if c]
                    except IndexError:
                        cls.raise_user_error('csv_format_error')
                    value = column.get_value(vals)

                if column.field.name == 'addresses' and row[cell]:
                    is_address = True
                    is_party = False
                    values[column.subfield.name] = value
                    if column.add_to_domain:
                        domain.append(
                            ('addresses.' + column.subfield.name, '=', value))
                    continue
                elif column.field.name == 'contact_mechanisms' and row[cell]:
                    is_contact = True
                    is_party = False
                    values[column.subfield.name] = value
                    if column.add_to_domain:
                        domain.append(
                            ('contact_mechanisms.' + column.subfield.name, '=',
                             value))
                    continue
                elif column.field.name == 'identifiers' and row[cell]:
                    identifiers.append({'code': value})
                elif row[cell]:
                    values[column.field.name] = value
                    domain.append((column.field.name, '=', value))

            # add values in rows
            if is_address and values:
                rows[-1]['record']['addresses'].append(values)
                if domain:
                    rows[-1]['domain'].append(domain)
            elif is_contact and values:
                rows[-1]['record']['contact_mechanisms'].append(values)
                if domain:
                    rows[-1]['domain'].append(domain)
            elif is_party and values:
                values['addresses'] = []
                values['contact_mechanisms'] = []
                values['identifiers'] = identifiers
                rows.append({
                    'record': values,
                    'domain': domain if domain else None,
                })

        # convert dict values to object and save
        logs = []
        to_save = []
        for row in rows:
            domain = row['domain']
            data = row['record']

            addresses = data['addresses']
            del data['addresses']
            contact_mechanisms = data['contact_mechanisms']
            del data['contact_mechanisms']
            identifiers = data['identifiers']
            del data['identifiers']

            # search record exist (party)
            if domain:
                records = Party.search(domain, limit=1)

                if skip_repeated and records:
                    logs.append(
                        cls.add_message_line(csv_file, 'skipped',
                                             'record_already_exists',
                                             (records[0].rec_name, )))
                    continue

                if update_record and records:
                    record, = records  # to update
                    logs.append(
                        cls.add_message_line(csv_file, 'done',
                                             'record_updated', (row)))
                else:
                    record = Party()  # to create
                    logs.append(
                        cls.add_message_line(csv_file, 'done', 'record_added',
                                             (row)))
            else:
                record = Party()  # to create
                logs.append(
                    cls.add_message_line(csv_file, 'done', 'record_added',
                                         (row)))

            # assign values to object record
            # (party, address, contact mechanism and identifier)
            for k, v in data.iteritems():
                setattr(record, k, v)
            if not hasattr(record, 'addresses'):
                record.addresses = ()
            if not hasattr(record, 'contact_mechanisms'):
                record.contact_mechanisms = ()
            if not hasattr(record, 'identifiers'):
                record.identifiers = ()

            addrs = ()
            for addr in addresses:
                address = Address()
                for k, v in addr.iteritems():
                    setattr(address, k, v)
                address.on_change_country()
                try:  # country_zip
                    address.on_change_zip()
                except:
                    pass
                addrs += (address, )
            if addrs:
                record.addresses += addrs

            cms = ()
            for cm in contact_mechanisms:
                contact = ContactMechanism()
                for k, v in cm.iteritems():
                    setattr(contact, k, v)
                cms += (contact, )
            if cms:
                record.contact_mechanisms += cms

            idens = ()
            for iden in identifiers:
                identifier = PartyIdentifier()
                for k, v in iden.iteritems():
                    setattr(identifier, k, v)
                idens += (identifier, )
            if idens:
                record.identifiers += idens

            to_save.append(record)  # to save

        state = 'done'
        if to_save:
            try:
                Party.save(to_save)
                logs.insert(
                    0,
                    cls.add_message_line(csv_file, 'done',
                                         'import_successfully',
                                         (len(to_save), )))
            except:
                state = 'error'
                logs.insert(
                    0,
                    cls.add_message_line(csv_file, 'error',
                                         'import_unsuccessfully',
                                         (len(to_save), )))

        cls.write([csv_file], {'state': state})
        Transaction().connection.commit()  # force to commit

        if profile_csv.email:
            cls.send_message('\n'.join(logs))

    @classmethod
    def import_file_sale(cls, csv_file):
        '''Sale Import CSV'''
        pool = Pool()

        profile_csv = csv_file.profile_csv
        has_header = profile_csv.header
        skip_repeated = csv_file.skip_repeated
        update_record = csv_file.update_record

        Sale = pool.get('sale.sale')
        Line = pool.get('sale.line')
        Product = pool.get('product.product')

        data = csv_file.read_csv_file()
        if has_header:
            next(data, None)

        # Create a new list with parent and child values for each record
        # Group CSV lines in same record. See party.csv example in test
        # rows = [{
        #    'record': {},
        #    'domain': [],
        #    }
        rows = []
        for row in data:
            is_sale = True
            is_line = False

            domain = []
            values = {}
            for column in profile_csv.columns:
                # each column, get value and assign in dict
                cells = column.column.split(',')
                cell = int(cells[0])

                if column.constant:
                    value = column.constant
                else:
                    try:
                        vals = [row[int(c)] for c in cells if c]
                    except IndexError:
                        cls.raise_user_error('csv_format_error')
                    value = column.get_value(vals)

                if column.field.name == 'line' and row[cell]:
                    is_line = True
                    is_sale = False
                    if column.subfield.name == 'product':
                        products = Product.search([
                            ('rec_name', '=', value),
                        ])
                        if products:
                            values[column.subfield.name] = value
                        else:
                            values['description'] = value
                    if column.add_to_domain:
                        domain.append(
                            ('lines.' + column.subfield.name, '=', value))
                    continue
                elif row[cell]:
                    values[column.field.name] = value
                    domain.append((column.field.name, '=', value))

            # add values in rows
            if is_line and values:
                rows[-1]['record']['lines'].append(values)
                if domain:
                    rows[-1]['domain'].append(domain)
            elif is_sale and values:
                values['lines'] = []
                rows.append({
                    'record': values,
                    'domain': domain if domain else None,
                })

        # convert dict values to object and save
        logs = []
        to_save = []
        for row in rows:
            domain = row['domain']
            data = row['record']

            lines = data['lines']
            del data['lines']

            # search record exist (party)
            if domain:
                records = Sale.search(domain, limit=1)

                if skip_repeated and records:
                    logs.append(
                        cls.add_message_line(csv_file, 'skipped',
                                             'record_already_exists',
                                             (records[0].rec_name, )))
                    continue

                if update_record and records:
                    record, = records  # to update
                    logs.append(
                        cls.add_message_line(csv_file, 'done',
                                             'record_updated', (row)))
                else:
                    record = Sale()  # to create
                    logs.append(
                        cls.add_message_line(csv_file, 'done', 'record_added',
                                             (row)))
            else:
                record = Sale()  # to create
                logs.append(
                    cls.add_message_line(csv_file, 'done', 'record_added',
                                         (row)))

            # assign values to object record
            # (sale and line)
            for k, v in data.iteritems():
                setattr(record, k, v)
            if not hasattr(record, 'lines'):
                record.lines = ()

            sale_lines = ()
            for l in lines:
                line = Line()
                for k, v in l.iteritems():
                    setattr(line, k, v)
                line.on_change_product()
                sale_lines += (line, )
            if sale_lines:
                record.lines += sale_lines

            to_save.append(record)  # to save

        state = 'done'
        if to_save:
            try:
                Sale.save(to_save)
                logs.insert(
                    0,
                    cls.add_message_line(csv_file, 'done',
                                         'import_successfully',
                                         (len(to_save), )))
            except:
                state = 'error'
                logs.insert(
                    0,
                    cls.add_message_line(csv_file, 'error',
                                         'import_unsuccessfully',
                                         (len(to_save), )))

        cls.write([csv_file], {'state': state})
        Transaction().connection.commit()  # force to commit

        if profile_csv.email:
            cls.send_message('\n'.join(logs))

    @classmethod
    @ModelView.button
    def import_file(cls, csv_files):
        '''Import CSV'''
        for csv_file in csv_files:
            profile_csv = csv_file.profile_csv
            import_csv = getattr(cls, 'import_file_%s' % profile_csv.method)
            import_csv(csv_file)
Ejemplo n.º 21
0
class Statement(Workflow, ModelSQL, ModelView):
    'Account Statement'
    __name__ = 'account.statement'
    name = fields.Char('Name', required=True)
    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        select=True,
        states=_STATES,
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=',
                      '!='), Eval('context', {}).get('company', -1)),
        ],
        depends=_DEPENDS)
    journal = fields.Many2One('account.statement.journal',
                              'Journal',
                              required=True,
                              select=True,
                              domain=[
                                  ('company', '=', Eval('company', -1)),
                              ],
                              states={
                                  'readonly': (Eval('state') != 'draft')
                                  | Eval('lines', [0]),
                              },
                              depends=['state', 'company'])
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'on_change_with_currency_digits')
    date = fields.Date('Date', required=True, select=True)
    start_balance = fields.Numeric('Start Balance',
                                   digits=(16, Eval('currency_digits', 2)),
                                   states=_BALANCE_STATES,
                                   depends=_BALANCE_DEPENDS +
                                   ['currency_digits'])
    end_balance = fields.Numeric('End Balance',
                                 digits=(16, Eval('currency_digits', 2)),
                                 states=_BALANCE_STATES,
                                 depends=_BALANCE_DEPENDS +
                                 ['currency_digits'])
    balance = fields.Function(
        fields.Numeric('Balance',
                       digits=(16, Eval('currency_digits', 2)),
                       states=_BALANCE_STATES,
                       depends=_BALANCE_DEPENDS + ['currency_digits']),
        'on_change_with_balance')
    total_amount = fields.Numeric('Total Amount',
                                  digits=(16, Eval('currency_digits', 2)),
                                  states=_AMOUNT_STATES,
                                  depends=_AMOUNT_DEPENDS +
                                  ['currency_digits'])
    number_of_lines = fields.Integer('Number of Lines',
                                     states=_NUMBER_STATES,
                                     depends=_NUMBER_DEPENDS)
    lines = fields.One2Many('account.statement.line',
                            'statement',
                            'Lines',
                            states={
                                'readonly':
                                (Eval('state') != 'draft') | ~Eval('journal'),
                            },
                            depends=['state', 'journal'])
    origins = fields.One2Many('account.statement.origin',
                              'statement',
                              "Origins",
                              states={
                                  'readonly': Eval('state') != 'draft',
                              },
                              depends=['state'])
    origin_file = fields.Binary("Origin File",
                                readonly=True,
                                file_id=file_id,
                                store_prefix=store_prefix)
    origin_file_id = fields.Char("Origin File ID", readonly=True)
    state = fields.Selection(STATES, 'State', readonly=True, select=True)
    validation = fields.Function(fields.Char('Validation'),
                                 'on_change_with_validation')

    @classmethod
    def __setup__(cls):
        super(Statement, cls).__setup__()
        cls._order[0] = ('id', 'DESC')
        cls._error_messages.update({
            'wrong_end_balance':
            'End Balance must be "%s".',
            'wrong_total_amount':
            'Total Amount must be "%s".',
            'wrong_number_of_lines':
            'Number of Lines must be "%s".',
            'delete_cancel': ('Statement "%s" must be cancelled before '
                              'deletion.'),
            'paid_invoice_draft_statement': ('There are paid invoices on '
                                             'draft statements.'),
            'debit_credit_account_statement_journal':
            ('Please provide '
             'debit and credit account on statement journal "%s".'),
            'post_with_pending_amount':
            ('Origin line "%(origin)s" '
             'of statement "%(statement)s" still has a pending amount '
             'of "%(amount)s".'),
        })
        cls._transitions |= set((
            ('draft', 'validated'),
            ('draft', 'cancel'),
            ('validated', 'posted'),
            ('validated', 'cancel'),
            ('cancel', 'draft'),
        ))
        cls._buttons.update({
            'draft': {
                'invisible': Eval('state') != 'cancel',
            },
            'validate_statement': {
                'invisible': Eval('state') != 'draft',
            },
            'post': {
                'invisible': Eval('state') != 'validated',
            },
            'cancel': {
                'invisible': ~Eval('state').in_(['draft', 'validated']),
            },
        })

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        transaction = Transaction()
        cursor = transaction.connection.cursor()
        sql_table = cls.__table__()

        # Migration from 1.8: new field company
        table = TableHandler(cls, module_name)
        company_exist = table.column_exist('company')

        super(Statement, cls).__register__(module_name)

        # Migration from 1.8: fill new field company
        if not company_exist:
            offset = 0
            limit = transaction.database.IN_MAX
            statements = True
            while statements:
                statements = cls.search([], offset=offset, limit=limit)
                offset += limit
                for statement in statements:
                    cls.write([statement], {
                        'company': statement.journal.company.id,
                    })
            table = TableHandler(cls, module_name)
            table.not_null_action('company', action='add')

        # Migration from 3.2: remove required on start/end balance
        table.not_null_action('start_balance', action='remove')
        table.not_null_action('end_balance', action='remove')

        # Migration from 3.2: add required name
        cursor.execute(*sql_table.update(
            [sql_table.name], [sql_table.id.cast(cls.name.sql_type().base)],
            where=sql_table.name == Null))

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_state():
        return 'draft'

    @staticmethod
    def default_date():
        Date = Pool().get('ir.date')
        return Date.today()

    @staticmethod
    def default_currency_digits():
        Company = Pool().get('company.company')
        if Transaction().context.get('company'):
            company = Company(Transaction().context['company'])
            return company.currency.digits
        return 2

    @fields.depends('journal', 'state', 'lines')
    def on_change_journal(self):
        if not self.journal:
            return

        statements = self.search([
            ('journal', '=', self.journal.id),
        ],
                                 order=[
                                     ('date', 'DESC'),
                                     ('id', 'DESC'),
                                 ],
                                 limit=1)
        if not statements:
            return

        statement, = statements
        self.start_balance = statement.end_balance

    @fields.depends('journal')
    def on_change_with_currency_digits(self, name=None):
        if self.journal:
            return self.journal.currency.digits
        return 2

    def get_end_balance(self, name):
        end_balance = self.start_balance
        for line in self.lines:
            end_balance += line.amount
        return end_balance

    @fields.depends('start_balance', 'end_balance')
    def on_change_with_balance(self, name=None):
        return ((getattr(self, 'end_balance', 0) or 0) -
                (getattr(self, 'start_balance', 0) or 0))

    @fields.depends('lines', 'journal')
    def on_change_lines(self):
        pool = Pool()
        Currency = pool.get('currency.currency')
        Line = pool.get('account.statement.line')
        if self.journal and self.lines:
            invoices = set()
            for line in self.lines:
                if getattr(line, 'invoice', None):
                    invoices.add(line.invoice)
            invoice_id2amount_to_pay = {}
            for invoice in invoices:
                with Transaction().set_context(date=invoice.currency_date):
                    if invoice.type == 'out':
                        sign = -1
                    else:
                        sign = 1
                    invoice_id2amount_to_pay[
                        invoice.id] = sign * (Currency.compute(
                            invoice.currency, invoice.amount_to_pay,
                            self.journal.currency))

            lines = list(self.lines)
            line_offset = 0
            for index, line in enumerate(self.lines or []):
                if getattr(line, 'invoice', None) and line.id:
                    amount_to_pay = invoice_id2amount_to_pay[line.invoice.id]
                    if (not self.journal.currency.is_zero(amount_to_pay)
                            and getattr(line, 'amount', None)
                            and (line.amount >= 0) == (amount_to_pay <= 0)):
                        if abs(line.amount) > abs(amount_to_pay):
                            new_line = Line()
                            for field_name, field in Line._fields.iteritems():
                                if field_name == 'id':
                                    continue
                                try:
                                    setattr(new_line, field_name,
                                            getattr(line, field_name))
                                except AttributeError:
                                    pass
                            new_line.amount = line.amount + amount_to_pay
                            new_line.invoice = None
                            line_offset += 1
                            lines.insert(index + line_offset, new_line)
                            invoice_id2amount_to_pay[line.invoice.id] = 0
                            line.amount = amount_to_pay.copy_sign(line.amount)
                        else:
                            invoice_id2amount_to_pay[line.invoice.id] = (
                                line.amount + amount_to_pay)
                    else:
                        line.invoice = None
            self.lines = lines

    @fields.depends('journal')
    def on_change_with_validation(self, name=None):
        if self.journal:
            return self.journal.validation

    def _group_key(self, line):
        key = (
            ('number', line.number or Unequal()),
            ('date', line.date),
            ('party', line.party),
        )
        return key

    def _get_grouped_line(self):
        "Return Line class for grouped lines"
        assert self.lines

        keys = [k[0] for k in self._group_key(self.lines[0])]

        class Line(namedtuple('Line', keys + ['lines'])):
            @property
            def amount(self):
                return sum((l.amount for l in self.lines))

            @property
            def descriptions(self):
                done = set()
                for line in self.lines:
                    if line.description and line.description not in done:
                        done.add(line.description)
                        yield line.description

        return Line

    @property
    def grouped_lines(self):
        if self.origins:
            for origin in self.origins:
                yield origin
        elif self.lines:
            Line = self._get_grouped_line()
            for key, lines in groupby(self.lines, key=self._group_key):
                yield Line(**dict(key + (('lines', list(lines)), )))

    @classmethod
    def delete(cls, statements):
        # Cancel before delete
        cls.cancel(statements)
        for statement in statements:
            if statement.state != 'cancel':
                cls.raise_user_error('delete_cancel', (statement.rec_name, ))
        super(Statement, cls).delete(statements)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, statements):
        pass

    def validate_balance(self):
        pool = Pool()
        Lang = pool.get('ir.lang')

        end_balance = (self.start_balance + sum(l.amount for l in self.lines))
        if end_balance != self.end_balance:
            lang, = Lang.search([
                ('code', '=', Transaction().language),
            ])
            amount = Lang.format(
                lang, '%.' + str(self.journal.currency.digits) + 'f',
                end_balance, True)
            self.raise_user_error('wrong_end_balance', amount)

    def validate_amount(self):
        pool = Pool()
        Lang = pool.get('ir.lang')

        amount = sum(l.amount for l in self.lines)
        if amount != self.total_amount:
            lang, = Lang.search([
                ('code', '=', Transaction().language),
            ])
            amount = Lang.format(
                lang, '%.' + str(self.journal.currency.digits) + 'f', amount,
                True)
            self.raise_user_error('wrong_total_amount', amount)

    def validate_number_of_lines(self):
        number = len(list(self.grouped_lines))
        if number != self.number_of_lines:
            self.raise_user_error('wrong_number_of_lines', number)

    @classmethod
    @ModelView.button
    @Workflow.transition('validated')
    def validate_statement(cls, statements):
        pool = Pool()
        Line = pool.get('account.statement.line')

        for statement in statements:
            getattr(statement, 'validate_%s' % statement.validation)()

        cls.create_move(statements)

        cls.write(statements, {
            'state': 'validated',
        })
        common_lines = Line.search([
            ('statement.state', '=', 'draft'),
            ('invoice.state', '=', 'paid'),
        ])
        if common_lines:
            warning_key = '_'.join(str(l.id) for l in common_lines)
            cls.raise_user_warning(warning_key, 'paid_invoice_draft_statement')
            Line.write(common_lines, {
                'invoice': None,
            })

    @classmethod
    def create_move(cls, statements):
        '''Create move for the statements and try to reconcile the lines.
        Returns the list of move, statement and lines
        '''
        pool = Pool()
        Line = pool.get('account.statement.line')
        Move = pool.get('account.move')
        MoveLine = pool.get('account.move.line')

        moves = []
        for statement in statements:
            for key, lines in groupby(statement.lines,
                                      key=statement._group_key):
                lines = list(lines)
                key = dict(key)
                move = statement._get_move(key)
                moves.append((move, statement, lines))

        Move.save([m for m, _, _ in moves])

        to_write = []
        for move, _, lines in moves:
            to_write.append(lines)
            to_write.append({
                'move': move.id,
            })
        if to_write:
            Line.write(*to_write)

        move_lines = []
        for move, statement, lines in moves:
            amount = 0
            amount_second_currency = 0
            for line in lines:
                move_line = line.get_move_line()
                move_line.move = move
                amount += move_line.debit - move_line.credit
                if move_line.amount_second_currency:
                    amount_second_currency += move_line.amount_second_currency
                move_lines.append((move_line, line))

            move_line = statement._get_move_line(amount,
                                                 amount_second_currency, lines)
            move_line.move = move
            move_lines.append((move_line, None))

        MoveLine.save([l for l, _ in move_lines])

        for move_line, line in move_lines:
            if line:
                line.reconcile(move_line)
        return moves

    def _get_move(self, key):
        'Return Move for the grouping key'
        pool = Pool()
        Move = pool.get('account.move')
        Period = pool.get('account.period')

        period_id = Period.find(self.company.id, date=key['date'])
        return Move(
            period=period_id,
            journal=self.journal.journal,
            date=key['date'],
            origin=self,
            company=self.company,
            description=unicode(key['number']),
        )

    def _get_move_line(self, amount, amount_second_currency, lines):
        'Return counterpart Move Line for the amount'
        pool = Pool()
        MoveLine = pool.get('account.move.line')

        if amount < 0:
            account = self.journal.journal.debit_account
        else:
            account = self.journal.journal.credit_account

        if not account:
            self.raise_user_error('debit_credit_account_statement_journal',
                                  (self.journal.rec_name, ))

        if self.journal.currency != self.company.currency:
            second_currency = self.journal.currency
            amount_second_currency *= -1
        else:
            second_currency = None
            amount_second_currency = None

        descriptions = {l.description for l in lines}
        if len(descriptions) == 1:
            description, = descriptions
        else:
            description = ''

        return MoveLine(
            debit=abs(amount) if amount < 0 else 0,
            credit=abs(amount) if amount > 0 else 0,
            account=account,
            second_currency=second_currency,
            amount_second_currency=amount_second_currency,
            description=description,
        )

    @classmethod
    @ModelView.button
    @Workflow.transition('posted')
    def post(cls, statements):
        StatementLine = Pool().get('account.statement.line')
        for statement in statements:
            for origin in statement.origins:
                if origin.pending_amount:
                    cls.raise_user_error(
                        'post_with_pending_amount', {
                            'origin': origin.rec_name,
                            'amount': origin.pending_amount,
                            'statement': statement.rec_name,
                        })

        lines = [l for s in statements for l in s.lines]
        StatementLine.post_move(lines)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancel')
    def cancel(cls, statements):
        StatementLine = Pool().get('account.statement.line')

        lines = [l for s in statements for l in s.lines]
        StatementLine.delete_move(lines)
Ejemplo n.º 22
0
class LoadPKCS12Start(ModelView):
    "Load PKCS12 Start"
    __name__ = "aeat.sii.load_pkcs12.start"

    pfx = fields.Binary('PFX File', required=True)
    password = fields.Char('Password', required=True)
Ejemplo n.º 23
0
class ActionReport(metaclass=PoolMeta):
    __name__ = 'ir.action.report'
    html_template = fields.Many2One('html.template',
                                    'Template',
                                    domain=[
                                        ('type', 'in', ['base', 'extension']),
                                    ],
                                    states={
                                        'invisible':
                                        Eval('template_extension') != 'jinja',
                                    },
                                    depends=['template_extension'])
    html_header_template = fields.Many2One(
        'html.template',
        'Header',
        domain=[
            ('type', '=', 'header'),
        ],
        states={
            'invisible': Eval('template_extension') != 'jinja',
        },
        depends=['template_extension'])
    html_footer_template = fields.Many2One(
        'html.template',
        'Footer',
        domain=[
            ('type', '=', 'footer'),
        ],
        states={
            'invisible': Eval('template_extension') != 'jinja',
        },
        depends=['template_extension'])
    html_last_footer_template = fields.Many2One(
        'html.template',
        'Last Footer',
        domain=[
            ('type', '=', 'footer'),
        ],
        states={
            'invisible': Eval('template_extension') != 'jinja',
        },
        depends=['template_extension'])
    html_templates = fields.One2Many('html.report.template',
                                     'report',
                                     'Templates',
                                     states={
                                         'invisible':
                                         Eval('template_extension') != 'jinja',
                                     },
                                     depends=['template_extension'])
    html_content = fields.Function(
        fields.Text('Content',
                    states={
                        'invisible': Eval('template_extension') != 'jinja',
                    },
                    depends=['template_extension']), 'get_content')

    html_translations = fields.One2Many('html.template.translation', 'report',
                                        'Translations')
    _html_translation_cache = Cache('html.template.translation',
                                    size_limit=10240,
                                    context=False)
    html_header_content = fields.Function(fields.Binary('Header Content'),
                                          'get_content')
    html_footer_content = fields.Function(fields.Binary('Footer Content'),
                                          'get_content')
    html_last_footer_content = fields.Function(
        fields.Binary('Last Footer Content'), 'get_content')

    @classmethod
    def __setup__(cls):
        super(ActionReport, cls).__setup__()

        jinja_option = ('jinja', 'Jinja')
        if jinja_option not in cls.template_extension.selection:
            cls.template_extension.selection.append(jinja_option)

    @classmethod
    def view_attributes(cls):
        return super(ActionReport, cls).view_attributes() + [
            ('//page[@id="html_report"]', 'states', {
                'invisible': Eval('template_extension') != 'jinja',
            })
        ]

    def get_content(self, name):
        obj_name = name.replace('content', 'template')
        obj = getattr(self, obj_name)
        if not obj:
            return

        content = []
        for template in self.html_templates:
            if template.template_used and template.template_used.all_content:
                content.append(template.template_used.all_content or '')
        content.append(obj.all_content or '')
        return '\n\n'.join(content)

    @classmethod
    def validate(cls, reports):
        for report in reports:
            report.check_template_jinja()

    def check_template_jinja(self):
        if self.template_extension == 'jinja':
            return
        missing, unused = self.get_missing_unused_signatures()
        if missing:
            raise UserError(
                gettext(
                    'html_report.missing_signatures', {
                        'template': self.rec_name,
                        'missing': '\n'.join(
                            sorted([x.rec_name for x in missing]))
                    }))
        if unused:
            raise UserError(
                gettext(
                    'html_report.unused_signatures', {
                        'template': self.rec_name,
                        'unused': '\n'.join(
                            sorted([x.rec_name for x in unused]))
                    }))

    def get_missing_unused_signatures(self):
        existing = {x.signature for x in self.html_templates}
        required = self.required_signatures()
        missing = required - existing
        unused = existing - required
        return missing, unused

    def required_signatures(self):
        if not self.html_template:
            return set()
        signatures = {x for x in self.html_template.uses}
        for template in self.html_templates:
            if not template.template:
                continue
            signatures |= {x for x in template.template.uses}
        return signatures

    @fields.depends('html_template', 'html_templates')
    def on_change_html_template(self):
        pool = Pool()
        Template = pool.get('html.template')
        ReportTemplate = pool.get('html.report.template')

        missing, unused = self.get_missing_unused_signatures()

        templates = list(self.html_templates)
        for signature in missing:
            record = ReportTemplate()
            record.signature = signature
            implementors = Template.search([('implements', '=', signature)])
            if len(implementors) == 1:
                record.template, = implementors
            templates.append(record)

        self.html_templates = templates

    @fields.depends('html_template', 'html_templates')
    def on_change_html_header_template(self):
        pool = Pool()
        Template = pool.get('html.template')
        ReportTemplate = pool.get('html.report.template')

        missing, unused = self.get_missing_unused_signatures()

        templates = list(self.html_templates)
        for signature in missing:
            record = ReportTemplate()
            record.signature = signature
            implementors = Template.search([('implements', '=', signature)])
            if len(implementors) == 1:
                record.template, = implementors
            templates.append(record)

        self.html_templates = templates

    @classmethod
    def gettext(cls, *args, **variables):
        HTMLTemplateTranslation = Pool().get('html.template.translation')

        report, src, lang = args
        key = (report, src, lang)
        text = cls._html_translation_cache.get(key)
        if text is None:
            translations = HTMLTemplateTranslation.search([
                ('report', '=', report),
                ('src', '=', src),
                ('lang', '=', lang),
            ],
                                                          limit=1)
            if translations:
                text = translations[0].value
            else:
                text = src
            cls._html_translation_cache.set(key, text)
        return text if not variables else text % variables
Ejemplo n.º 24
0
class ActionReport(ActionMixin, ModelSQL, ModelView):
    "Action report"
    __name__ = 'ir.action.report'
    _action_name = 'report_name'
    model = fields.Char('Model')
    report_name = fields.Char('Internal Name', required=True)
    report = fields.Char("Path",
                         states={
                             'invisible': Eval('is_custom', False),
                         },
                         depends=['is_custom'])
    report_content_custom = fields.Binary('Content')
    is_custom = fields.Function(fields.Boolean("Is Custom"), 'get_is_custom')
    report_content = fields.Function(fields.Binary(
        'Content', filename='report_content_name'),
                                     'get_report_content',
                                     setter='set_report_content')
    report_content_name = fields.Function(
        fields.Char('Content Name'), 'on_change_with_report_content_name')
    report_content_html = fields.Function(fields.Binary(
        "Content HTML",
        states={
            'invisible': ~Eval('template_extension').in_(['html', 'xhtml']),
        },
        depends=['template_extension']),
                                          'get_report_content_html',
                                          setter='set_report_content_html')
    action = fields.Many2One('ir.action',
                             'Action',
                             required=True,
                             ondelete='CASCADE')
    direct_print = fields.Boolean('Direct Print')
    single = fields.Boolean(
        "Single", help="Check if the template works only for one record.")
    translatable = fields.Boolean(
        "Translatable",
        help="Uncheck to disable translations for this report.")
    template_extension = fields.Selection([
        ('odt', 'OpenDocument Text'),
        ('odp', 'OpenDocument Presentation'),
        ('ods', 'OpenDocument Spreadsheet'),
        ('odg', 'OpenDocument Graphics'),
        ('txt', 'Plain Text'),
        ('xml', 'XML'),
        ('html', 'HTML'),
        ('xhtml', 'XHTML'),
    ],
                                          string='Template Extension',
                                          required=True,
                                          translate=False)
    extension = fields.Selection(
        [
            ('', ''),
            ('bib', 'BibTex'),
            ('bmp', 'Windows Bitmap'),
            ('csv', 'Text CSV'),
            ('dbf', 'dBase'),
            ('dif', 'Data Interchange Format'),
            ('doc', 'Microsoft Word 97/2000/XP'),
            ('doc6', 'Microsoft Word 6.0'),
            ('doc95', 'Microsoft Word 95'),
            ('docbook', 'DocBook'),
            ('docx', 'Microsoft Office Open XML Text'),
            ('docx7', 'Microsoft Word 2007 XML'),
            ('emf', 'Enhanced Metafile'),
            ('eps', 'Encapsulated PostScript'),
            ('gif', 'Graphics Interchange Format'),
            ('html', 'HTML Document'),
            ('jpg', 'Joint Photographic Experts Group'),
            ('met', 'OS/2 Metafile'),
            ('ooxml', 'Microsoft Office Open XML'),
            ('pbm', 'Portable Bitmap'),
            ('pct', 'Mac Pict'),
            ('pdb', 'AportisDoc (Palm)'),
            ('pdf', 'Portable Document Format'),
            ('pgm', 'Portable Graymap'),
            ('png', 'Portable Network Graphic'),
            ('ppm', 'Portable Pixelmap'),
            ('ppt', 'Microsoft PowerPoint 97/2000/XP'),
            ('psw', 'Pocket Word'),
            ('pwp', 'PlaceWare'),
            ('pxl', 'Pocket Excel'),
            ('ras', 'Sun Raster Image'),
            ('rtf', 'Rich Text Format'),
            ('latex', 'LaTeX 2e'),
            ('sda', 'StarDraw 5.0 (OpenOffice.org Impress)'),
            ('sdc', 'StarCalc 5.0'),
            ('sdc4', 'StarCalc 4.0'),
            ('sdc3', 'StarCalc 3.0'),
            ('sdd', 'StarImpress 5.0'),
            ('sdd3', 'StarDraw 3.0 (OpenOffice.org Impress)'),
            ('sdd4', 'StarImpress 4.0'),
            ('sdw', 'StarWriter 5.0'),
            ('sdw4', 'StarWriter 4.0'),
            ('sdw3', 'StarWriter 3.0'),
            ('slk', 'SYLK'),
            ('svg', 'Scalable Vector Graphics'),
            ('svm', 'StarView Metafile'),
            ('swf', 'Macromedia Flash (SWF)'),
            ('sxc', 'OpenOffice.org 1.0 Spreadsheet'),
            ('sxi', 'OpenOffice.org 1.0 Presentation'),
            ('sxd', 'OpenOffice.org 1.0 Drawing'),
            ('sxd3', 'StarDraw 3.0'),
            ('sxd5', 'StarDraw 5.0'),
            ('sxw', 'Open Office.org 1.0 Text Document'),
            ('text', 'Text Encoded'),
            ('tiff', 'Tagged Image File Format'),
            ('txt', 'Plain Text'),
            ('wmf', 'Windows Metafile'),
            ('xhtml', 'XHTML Document'),
            ('xls', 'Microsoft Excel 97/2000/XP'),
            ('xls5', 'Microsoft Excel 5.0'),
            ('xls95', 'Microsoft Excel 95'),
            ('xlsx', 'Microsoft Excel 2007/2010 XML'),
            ('xpm', 'X PixMap'),
        ],
        translate=False,
        string='Extension',
        help='Leave empty for the same as template, '
        'see LibreOffice documentation for compatible format.')
    module = fields.Char('Module', readonly=True, select=True)
    _template_cache = MemoryCache('ir.action.report.template', context=False)

    @classmethod
    def __register__(cls, module_name):
        super(ActionReport, cls).__register__(module_name)

        transaction = Transaction()
        cursor = transaction.connection.cursor()
        table = cls.__table_handler__(module_name)
        action_report = cls.__table__()

        # Migration from 3.4 remove report_name_module_uniq constraint
        table.drop_constraint('report_name_module_uniq')

        # Migration from 4.4 replace plain extension by txt
        cursor.execute(
            *action_report.update([action_report.extension], ['txt'],
                                  where=action_report.extension == 'plain'))

    @staticmethod
    def default_type():
        return 'ir.action.report'

    @staticmethod
    def default_report_content():
        return None

    @staticmethod
    def default_direct_print():
        return False

    @classmethod
    def default_single(cls):
        return False

    @classmethod
    def default_translatable(cls):
        return True

    @staticmethod
    def default_template_extension():
        return 'odt'

    @staticmethod
    def default_extension():
        return ''

    @staticmethod
    def default_module():
        return Transaction().context.get('module') or ''

    def get_is_custom(self, name):
        return bool(self.report_content_custom)

    @classmethod
    def get_report_content(cls, reports, name):
        contents = {}
        converter = fields.Binary.cast
        default = None
        format_ = Transaction().context.get('%s.%s' % (cls.__name__, name), '')
        if format_ == 'size':
            converter = len
            default = 0
        for report in reports:
            data = getattr(report, name + '_custom')
            if not data and getattr(report, name[:-8]):
                try:
                    with file_open(getattr(report,
                                           name[:-8]).replace('/', os.sep),
                                   mode='rb') as fp:
                        data = fp.read()
                except Exception:
                    data = None
            contents[report.id] = converter(data) if data else default
        return contents

    @classmethod
    def set_report_content(cls, records, name, value):
        cls.write(records, {'%s_custom' % name: value})

    @classmethod
    def get_report_content_html(cls, reports, name):
        return cls.get_report_content(reports, name[:-5])

    @classmethod
    def set_report_content_html(cls, reports, name, value):
        if value is not None:
            value = value.encode('utf-8')
        cls.set_report_content(reports, name[:-5], value)

    @fields.depends('name', 'template_extension')
    def on_change_with_report_content_name(self, name=None):
        return ''.join(
            filter(None, [self.name, os.extsep, self.template_extension]))

    @classmethod
    def get_pyson(cls, reports, name):
        pysons = {}
        field = name[6:]
        defaults = {}
        for report in reports:
            pysons[report.id] = (getattr(report, field)
                                 or defaults.get(field, 'null'))
        return pysons

    @classmethod
    def copy(cls, reports, default=None):
        if default is None:
            default = {}
        default = default.copy()
        default.setdefault('module', None)

        new_reports = []
        for report in reports:
            if report.report:
                default['report_content'] = None
                default['report'] = None
            default['report_name'] = report.report_name
            new_reports.extend(
                super(ActionReport, cls).copy([report], default=default))
        return new_reports

    @classmethod
    def write(cls, reports, values, *args):
        context = Transaction().context
        if 'module' in context:
            actions = iter((reports, values) + args)
            args = []
            for reports, values in zip(actions, actions):
                values = values.copy()
                values['module'] = context['module']
                args.extend((reports, values))
            reports, values = args[:2]
            args = args[2:]
        cls._template_cache.clear()
        super(ActionReport, cls).write(reports, values, *args)

    def get_template_cached(self):
        return self._template_cache.get(self.id)

    def set_template_cached(self, template):
        self._template_cache.set(self.id, template)
Ejemplo n.º 25
0
class Statement(Workflow, ModelSQL, ModelView):
    'Account Statement'
    __name__ = 'account.statement'

    _states = {'readonly': Eval('state') != 'draft'}
    _balance_states = _states.copy()
    _balance_states.update({
            'invisible': ~Eval('validation', '').in_(['balance']),
            'required': Eval('validation', '').in_(['balance']),
            })
    _amount_states = _states.copy()
    _amount_states.update({
            'invisible': ~Eval('validation', '').in_(['amount']),
            'required': Eval('validation', '').in_(['amount']),
            })
    _number_states = _states.copy()
    _number_states.update({
            'invisible': ~Eval('validation', '').in_(['number_of_lines']),
            'required': Eval('validation', '').in_(['number_of_lines']),
            })

    name = fields.Char('Name', required=True)
    company = fields.Many2One(
        'company.company', "Company", required=True, select=True,
        states=_states)
    journal = fields.Many2One('account.statement.journal', 'Journal',
        required=True, select=True,
        domain=[
            ('company', '=', Eval('company', -1)),
            ],
        states={
            'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
            })
    currency = fields.Function(fields.Many2One(
            'currency.currency', "Currency"), 'on_change_with_currency')
    date = fields.Date('Date', required=True, select=True)
    start_balance = Monetary(
        "Start Balance", currency='currency', digits='currency',
        states=_balance_states)
    end_balance = Monetary(
        "End Balance", currency='currency', digits='currency',
        states=_balance_states)
    balance = fields.Function(Monetary(
            "Balance", currency='currency', digits='currency',
            states=_balance_states),
        'on_change_with_balance')
    total_amount = Monetary(
        "Total Amount", currency='currency', digits='currency',
        states=_amount_states)
    number_of_lines = fields.Integer('Number of Lines',
        states=_number_states)
    lines = fields.One2Many('account.statement.line', 'statement',
        'Lines', states={
            'readonly': (Eval('state') != 'draft') | ~Eval('journal'),
            })
    origins = fields.One2Many('account.statement.origin', 'statement',
        "Origins", states={
            'readonly': Eval('state') != 'draft',
            })
    origin_file = fields.Binary(
        "Origin File", readonly=True,
        file_id=file_id, store_prefix=store_prefix)
    origin_file_id = fields.Char("Origin File ID", readonly=True)
    state = fields.Selection([
            ('draft', "Draft"),
            ('validated', "Validated"),
            ('cancelled', "Cancelled"),
            ('posted', "Posted"),
            ], "State", readonly=True, select=True, sort=False)
    validation = fields.Function(fields.Char('Validation'),
        'on_change_with_validation')
    to_reconcile = fields.Function(
        fields.Boolean("To Reconcile"), 'get_to_reconcile')

    del _states
    del _balance_states
    del _amount_states
    del _number_states

    @classmethod
    def __setup__(cls):
        super(Statement, cls).__setup__()
        cls._order[0] = ('id', 'DESC')
        cls._transitions |= set((
                ('draft', 'validated'),
                ('draft', 'cancelled'),
                ('validated', 'posted'),
                ('validated', 'cancelled'),
                ('cancelled', 'draft'),
                ))
        cls._buttons.update({
                'draft': {
                    'invisible': Eval('state') != 'cancelled',
                    'depends': ['state'],
                    },
                'validate_statement': {
                    'invisible': Eval('state') != 'draft',
                    'depends': ['state'],
                    },
                'post': {
                    'invisible': Eval('state') != 'validated',
                    'depends': ['state'],
                    },
                'cancel': {
                    'invisible': ~Eval('state').in_(['draft', 'validated']),
                    'depends': ['state'],
                    },
                'reconcile': {
                    'invisible': Eval('state').in_(['draft', 'cancelled']),
                    'readonly': ~Eval('to_reconcile'),
                    'depends': ['state', 'to_reconcile'],
                    },
                })
        cls.__rpc__.update({
                'post': RPC(
                    readonly=False, instantiate=0, fresh_session=True),
                })

    @classmethod
    def __register__(cls, module_name):
        transaction = Transaction()
        cursor = transaction.connection.cursor()
        sql_table = cls.__table__()

        super(Statement, cls).__register__(module_name)
        table = cls.__table_handler__(module_name)

        # Migration from 3.2: remove required on start/end balance
        table.not_null_action('start_balance', action='remove')
        table.not_null_action('end_balance', action='remove')

        # Migration from 3.2: add required name
        cursor.execute(*sql_table.update([sql_table.name],
                [sql_table.id.cast(cls.name.sql_type().base)],
                where=sql_table.name == Null))

        # Migration from 5.6: rename state cancel to cancelled
        cursor.execute(*sql_table.update(
                [sql_table.state], ['cancelled'],
                where=sql_table.state == 'cancel'))

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_state():
        return 'draft'

    @staticmethod
    def default_date():
        Date = Pool().get('ir.date')
        return Date.today()

    @fields.depends('journal', 'state', 'lines')
    def on_change_journal(self):
        if not self.journal:
            return

        statements = self.search([
                ('journal', '=', self.journal.id),
                ], order=[
                ('date', 'DESC'),
                ('id', 'DESC'),
                ], limit=1)
        if not statements:
            return

        statement, = statements
        self.start_balance = statement.end_balance

    @fields.depends('journal')
    def on_change_with_currency(self, name=None):
        if self.journal:
            return self.journal.currency.id

    @fields.depends('start_balance', 'end_balance')
    def on_change_with_balance(self, name=None):
        return ((getattr(self, 'end_balance', 0) or 0)
            - (getattr(self, 'start_balance', 0) or 0))

    @fields.depends('origins', 'lines', 'journal', 'company')
    def on_change_origins(self):
        if not self.journal or not self.origins or not self.company:
            return
        if self.journal.currency != self.company.currency:
            return

        invoices = set()
        for line in self.lines:
            if (line.invoice
                    and line.invoice.currency == self.company.currency):
                invoices.add(line.invoice)
        for origin in self.origins:
            for line in origin.lines:
                if (line.invoice
                        and line.invoice.currency == self.company.currency):
                    invoices.add(line.invoice)
        invoice_id2amount_to_pay = {}
        for invoice in invoices:
            if invoice.type == 'out':
                sign = -1
            else:
                sign = 1
            invoice_id2amount_to_pay[invoice.id] = sign * invoice.amount_to_pay

        origins = list(self.origins)
        for origin in origins:
            lines = list(origin.lines)
            for line in lines:
                if (line.invoice
                        and line.id
                        and line.invoice.id in invoice_id2amount_to_pay):
                    amount_to_pay = invoice_id2amount_to_pay[line.invoice.id]
                    if (amount_to_pay
                            and getattr(line, 'amount', None)
                            and (line.amount >= 0) == (amount_to_pay <= 0)):
                        if abs(line.amount) > abs(amount_to_pay):
                            line.amount = amount_to_pay.copy_sign(line.amount)
                        else:
                            invoice_id2amount_to_pay[line.invoice.id] = (
                                line.amount + amount_to_pay)
                    else:
                        line.invoice = None
            origin.lines = lines
        self.origins = origins

    @fields.depends('lines', 'journal', 'company')
    def on_change_lines(self):
        pool = Pool()
        Line = pool.get('account.statement.line')
        if not self.journal or not self.lines or not self.company:
            return
        if self.journal.currency != self.company.currency:
            return

        invoices = set()
        for line in self.lines:
            if (line.invoice
                    and line.invoice.currency == self.company.currency):
                invoices.add(line.invoice)
        invoice_id2amount_to_pay = {}
        for invoice in invoices:
            if invoice.type == 'out':
                sign = -1
            else:
                sign = 1
            invoice_id2amount_to_pay[invoice.id] = sign * invoice.amount_to_pay

        lines = list(self.lines)
        line_offset = 0
        for index, line in enumerate(self.lines or []):
            if line.invoice and line.id:
                if line.invoice.id not in invoice_id2amount_to_pay:
                    continue
                amount_to_pay = invoice_id2amount_to_pay[line.invoice.id]
                if (amount_to_pay
                        and getattr(line, 'amount', None)
                        and (line.amount >= 0) == (amount_to_pay <= 0)):
                    if abs(line.amount) > abs(amount_to_pay):
                        new_line = Line()
                        for field_name, field in Line._fields.items():
                            if field_name == 'id':
                                continue
                            try:
                                setattr(new_line, field_name,
                                    getattr(line, field_name))
                            except AttributeError:
                                pass
                        new_line.amount = line.amount + amount_to_pay
                        new_line.invoice = None
                        line_offset += 1
                        lines.insert(index + line_offset, new_line)
                        invoice_id2amount_to_pay[line.invoice.id] = 0
                        line.amount = amount_to_pay.copy_sign(line.amount)
                    else:
                        invoice_id2amount_to_pay[line.invoice.id] = (
                            line.amount + amount_to_pay)
                else:
                    line.invoice = None
        self.lines = lines

    @fields.depends('journal')
    def on_change_with_validation(self, name=None):
        if self.journal:
            return self.journal.validation

    def get_to_reconcile(self, name=None):
        return bool(self.lines_to_reconcile)

    @property
    def lines_to_reconcile(self):
        lines = []
        for line in self.lines:
            if line.move:
                for move_line in line.move.lines:
                    if (move_line.account.reconcile
                            and not move_line.reconciliation):
                        lines.append(move_line)
        return lines

    def _group_key(self, line):
        key = (
            ('number', line.number or Unequal()),
            ('date', line.date),
            ('party', line.party),
            )
        return key

    def _get_grouped_line(self):
        "Return Line class for grouped lines"
        lines = self.origins or self.lines
        assert lines

        keys = [k[0] for k in self._group_key(lines[0])]

        class Line(namedtuple('Line', keys + ['lines'])):

            @property
            def amount(self):
                return sum((l.amount for l in self.lines))

            @property
            def descriptions(self):
                done = set()
                for line in self.lines:
                    if line.description and line.description not in done:
                        done.add(line.description)
                        yield line.description
        return Line

    @property
    def grouped_lines(self):
        if self.origins:
            lines = self.origins
        elif self.lines:
            lines = self.lines
        else:
            return
        Line = self._get_grouped_line()
        for key, lines in groupby(lines, key=self._group_key):
            yield Line(**dict(key + (('lines', list(lines)),)))

    @classmethod
    def view_attributes(cls):
        return super().view_attributes() + [
            ('/tree', 'visual', If(Eval('state') == 'cancelled', 'muted', '')),
            ]

    @classmethod
    def delete(cls, statements):
        # Cancel before delete
        cls.cancel(statements)
        for statement in statements:
            if statement.state != 'cancelled':
                raise AccessError(
                    gettext('account_statement.msg_statement_delete_cancel',
                        statement=statement.rec_name))
        super(Statement, cls).delete(statements)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, statements):
        pass

    def validate_balance(self):
        pool = Pool()
        Lang = pool.get('ir.lang')

        amount = (self.start_balance
            + sum(l.amount for l in self.lines))
        if amount != self.end_balance:
            lang = Lang.get()
            end_balance = lang.currency(
                self.end_balance, self.journal.currency)
            amount = lang.currency(amount, self.journal.currency)
            raise StatementValidateError(
                gettext('account_statement.msg_statement_wrong_end_balance',
                    statement=self.rec_name,
                    end_balance=end_balance,
                    amount=amount))

    def validate_amount(self):
        pool = Pool()
        Lang = pool.get('ir.lang')

        amount = sum(l.amount for l in self.lines)
        if amount != self.total_amount:
            lang = Lang.get()
            total_amount = lang.currency(
                self.total_amount, self.journal.currency)
            amount = lang.currency(amount, self.journal.currency)
            raise StatementValidateError(
                gettext('account_statement.msg_statement_wrong_total_amount',
                    statement=self.rec_name,
                    total_amount=total_amount,
                    amount=amount))

    def validate_number_of_lines(self):
        number = len(list(self.grouped_lines))
        if number > self.number_of_lines:
            raise StatementValidateError(
                gettext('account_statement'
                    '.msg_statement_wrong_number_of_lines_remove',
                    statement=self.rec_name,
                    n=number - self.number_of_lines))
        elif number < self.number_of_lines:
            raise StatementValidateError(
                gettext('account_statement'
                    '.msg_statement_wrong_number_of_lines_remove',
                    statement=self.rec_name,
                    n=self.number_of_lines - number))

    @classmethod
    @ModelView.button
    @Workflow.transition('validated')
    def validate_statement(cls, statements):
        pool = Pool()
        Line = pool.get('account.statement.line')
        Warning = pool.get('res.user.warning')
        paid_cancelled_invoice_lines = []
        for statement in statements:
            getattr(statement, 'validate_%s' % statement.validation)()
            paid_cancelled_invoice_lines.extend(l for l in statement.lines
                if l.invoice and l.invoice.state in {'cancelled', 'paid'})

        if paid_cancelled_invoice_lines:
            warning_key = Warning.format(
                'statement_paid_cancelled_invoice_lines',
                paid_cancelled_invoice_lines)
            if Warning.check(warning_key):
                raise StatementValidateWarning(warning_key,
                    gettext('account_statement'
                        '.msg_statement_invoice_paid_cancelled'))
            Line.write(paid_cancelled_invoice_lines, {
                    'related_to': None,
                    })

        cls.create_move(statements)

        cls.write(statements, {
                'state': 'validated',
                })
        common_lines = [l for l in Line.search([
                    ('statement.state', '=', 'draft'),
                    ('related_to.state', 'in', ['posted', 'paid'],
                        'account.invoice'),
                    ])
            if l.invoice.reconciled]
        if common_lines:
            warning_key = '_'.join(str(l.id) for l in common_lines)
            if Warning.check(warning_key):
                raise StatementValidateWarning(warning_key,
                    gettext('account_statement'
                        '.msg_statement_paid_invoice_draft'))
            Line.write(common_lines, {
                    'related_to': None,
                    })

    @classmethod
    def create_move(cls, statements):
        '''Create move for the statements and try to reconcile the lines.
        Returns the list of move, statement and lines
        '''
        pool = Pool()
        Line = pool.get('account.statement.line')
        Move = pool.get('account.move')
        MoveLine = pool.get('account.move.line')

        moves = []
        for statement in statements:
            for key, lines in groupby(
                    statement.lines, key=statement._group_key):
                lines = list(lines)
                key = dict(key)
                move = statement._get_move(key)
                moves.append((move, statement, lines))

        Move.save([m for m, _, _ in moves])

        to_write = []
        for move, _, lines in moves:
            to_write.append(lines)
            to_write.append({
                    'move': move.id,
                    })
        if to_write:
            Line.write(*to_write)

        move_lines = []
        for move, statement, lines in moves:
            amount = 0
            amount_second_currency = 0
            for line in lines:
                move_line = line.get_move_line()
                move_line.move = move
                amount += move_line.debit - move_line.credit
                if move_line.amount_second_currency:
                    amount_second_currency += move_line.amount_second_currency
                move_lines.append((move_line, line))

            move_line = statement._get_move_line(
                amount, amount_second_currency, lines)
            move_line.move = move
            move_lines.append((move_line, None))

        MoveLine.save([l for l, _ in move_lines])

        Line.reconcile(move_lines)
        return moves

    def _get_move(self, key):
        'Return Move for the grouping key'
        pool = Pool()
        Move = pool.get('account.move')
        Period = pool.get('account.period')

        period_id = Period.find(self.company.id, date=key['date'])
        return Move(
            period=period_id,
            journal=self.journal.journal,
            date=key['date'],
            origin=self,
            company=self.company,
            description=str(key['number']),
            )

    def _get_move_line(self, amount, amount_second_currency, lines):
        'Return counterpart Move Line for the amount'
        pool = Pool()
        MoveLine = pool.get('account.move.line')

        if self.journal.currency != self.company.currency:
            second_currency = self.journal.currency
            amount_second_currency *= -1
        else:
            second_currency = None
            amount_second_currency = None

        descriptions = {l.description for l in lines}
        if len(descriptions) == 1:
            description, = descriptions
        else:
            description = ''

        return MoveLine(
            debit=abs(amount) if amount < 0 else 0,
            credit=abs(amount) if amount > 0 else 0,
            account=self.journal.account,
            second_currency=second_currency,
            amount_second_currency=amount_second_currency,
            description=description,
            )

    @classmethod
    @ModelView.button
    @Workflow.transition('posted')
    def post(cls, statements):
        pool = Pool()
        Lang = pool.get('ir.lang')
        StatementLine = pool.get('account.statement.line')
        for statement in statements:
            for origin in statement.origins:
                if origin.pending_amount:
                    lang = Lang.get()
                    amount = lang.currency(
                        origin.pending_amount, statement.journal.currency)
                    raise StatementPostError(
                        gettext('account_statement'
                            '.msg_statement_post_pending_amount',
                            statement=statement.rec_name,
                            amount=amount,
                            origin=origin.rec_name))
        # Write state to skip statement test on Move.post
        cls.write(statements, {'state': 'posted'})
        lines = [l for s in statements for l in s.lines]
        StatementLine.post_move(lines)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, statements):
        StatementLine = Pool().get('account.statement.line')

        lines = [l for s in statements for l in s.lines]
        StatementLine.delete_move(lines)

    @classmethod
    @ModelView.button_action('account_statement.act_reconcile')
    def reconcile(cls, statements):
        pass

    @classmethod
    def copy(cls, statements, default=None):
        default = default.copy() if default is not None else {}
        new_statements = []
        for origins, sub_statements in groupby(
                statements, key=lambda s: bool(s.origins)):
            sub_statements = list(sub_statements)
            sub_default = default.copy()
            if origins:
                sub_default.setdefault('lines')
            new_statements.extend(super().copy(
                    statements, default=sub_default))
        return new_statements
Ejemplo n.º 26
0
class ActionActWindow(ActionMixin, ModelSQL, ModelView):
    "Action act window"
    __name__ = 'ir.action.act_window'
    domain = fields.Char('Domain Value')
    context = fields.Char('Context Value')
    order = fields.Char('Order Value')
    res_model = fields.Char('Model')
    context_model = fields.Char('Context Model')
    context_domain = fields.Char(
        "Context Domain",
        help="Part of the domain that will be evaluated on each refresh.")
    act_window_views = fields.One2Many('ir.action.act_window.view',
                                       'act_window', 'Views')
    views = fields.Function(fields.Binary('Views'), 'get_views')
    act_window_domains = fields.One2Many('ir.action.act_window.domain',
                                         'act_window', 'Domains')
    domains = fields.Function(fields.Binary('Domains'), 'get_domains')
    limit = fields.Integer('Limit', help='Default limit for the list view.')
    action = fields.Many2One('ir.action',
                             'Action',
                             required=True,
                             ondelete='CASCADE')
    search_value = fields.Char(
        'Search Criteria', help='Default search criteria for the list view.')
    pyson_domain = fields.Function(fields.Char('PySON Domain'), 'get_pyson')
    pyson_context = fields.Function(fields.Char('PySON Context'), 'get_pyson')
    pyson_order = fields.Function(fields.Char('PySON Order'), 'get_pyson')
    pyson_search_value = fields.Function(fields.Char('PySON Search Criteria'),
                                         'get_pyson')

    @classmethod
    def __setup__(cls):
        super(ActionActWindow, cls).__setup__()
        cls.__rpc__.update({
            'get': RPC(cache=dict(days=1)),
        })

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().connection.cursor()
        act_window = cls.__table__()
        super(ActionActWindow, cls).__register__(module_name)

        table = cls.__table_handler__(module_name)

        # Migration from 3.0: auto_refresh removed
        table.drop_column('auto_refresh')

        # Migration from 4.0: window_name removed
        table.drop_column('window_name')

        # Migration from 4.2: remove required on limit
        table.not_null_action('limit', 'remove')
        cursor.execute(*act_window.update([act_window.limit], [Null],
                                          where=act_window.limit == 0))

    @staticmethod
    def default_type():
        return 'ir.action.act_window'

    @staticmethod
    def default_context():
        return '{}'

    @staticmethod
    def default_search_value():
        return '[]'

    @classmethod
    def validate(cls, actions):
        super(ActionActWindow, cls).validate(actions)
        cls.check_views(actions)
        cls.check_domain(actions)
        cls.check_context(actions)

    @classmethod
    def check_views(cls, actions):
        "Check views"
        for action in actions:
            if action.res_model:
                for act_window_view in action.act_window_views:
                    view = act_window_view.view
                    if view.model != action.res_model:
                        raise ViewError(
                            gettext('ir.msg_action_invalid_views',
                                    view=view.rec_name,
                                    action=action.rec_name))
                    if view.type == 'board':
                        raise ViewError(
                            gettext('ir.msg_action_invalid_views',
                                    view=view.rec_name,
                                    action=action.rec_name))
            else:
                for act_window_view in action.act_window_views:
                    view = act_window_view.view
                    if view.model:
                        raise ViewError(
                            gettext('ir.msg_action_invalid_views',
                                    view=view.rec_name,
                                    action=action.rec_name))
                    if view.type != 'board':
                        raise ViewError(
                            gettext('ir.msg_action_invalid_views',
                                    view=view.rec_name,
                                    action=action.rec_name))

    @classmethod
    def check_domain(cls, actions):
        "Check domain and search_value"
        for action in actions:
            for domain in (action.domain, action.search_value):
                if not domain:
                    continue
                try:
                    value = PYSONDecoder().decode(domain)
                except Exception:
                    raise DomainError(
                        gettext('ir.msg_action_invalid_domain',
                                domain=domain,
                                action=action.rec_name))
                if isinstance(value, PYSON):
                    if not value.types() == set([list]):
                        raise DomainError(
                            gettext('ir.msg_action_invalid_domain',
                                    domain=domain,
                                    action=action.rec_name))
                elif not isinstance(value, list):
                    raise DomainError(
                        gettext('ir.msg_action_invalid_domain',
                                domain=domain,
                                action=action.rec_name))
                else:
                    try:
                        fields.domain_validate(value)
                    except Exception:
                        raise DomainError(
                            gettext('ir.msg_action_invalid_domain',
                                    domain=domain,
                                    action=action.rec_name))

    @classmethod
    def check_context(cls, actions):
        "Check context"
        for action in actions:
            if action.context:
                try:
                    value = PYSONDecoder().decode(action.context)
                except Exception:
                    raise ContextError(
                        gettext('ir.msg_action_invalid_context',
                                context=action.context,
                                action=action.rec_name))
                if isinstance(value, PYSON):
                    if not value.types() == set([dict]):
                        raise ContextError(
                            gettext('ir.msg_action_invalid_context',
                                    context=action.context,
                                    action=action.rec_name))
                elif not isinstance(value, dict):
                    raise ContextError(
                        gettext('ir.msg_action_invalid_context',
                                context=action.context,
                                action=action.rec_name))
                else:
                    try:
                        fields.context_validate(value)
                    except Exception:
                        raise ContextError(
                            gettext('ir.msg_action_invalid_context',
                                    context=action.context,
                                    action=action.rec_name))

    def get_views(self, name):
        return [(view.view.id, view.view.type)
                for view in self.act_window_views]

    def get_domains(self, name):
        return [(domain.name, domain.domain or '[]', domain.count)
                for domain in self.act_window_domains]

    @classmethod
    def get_pyson(cls, windows, name):
        pool = Pool()
        encoder = PYSONEncoder()
        pysons = {}
        field = name[6:]
        defaults = {
            'domain': '[]',
            'context': '{}',
            'search_value': '[]',
        }
        for window in windows:
            if not window.order and field == 'order':
                if window.res_model:
                    defaults['order'] = encoder.encode(
                        getattr(pool.get(window.res_model), '_order', 'null'))
                else:
                    defaults['order'] = 'null'
            pysons[window.id] = (getattr(window, field)
                                 or defaults.get(field, 'null'))
        return pysons

    @classmethod
    def get(cls, xml_id):
        'Get values from XML id or id'
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        Action = pool.get('ir.action')
        if '.' in xml_id:
            action_id = ModelData.get_id(*xml_id.split('.'))
        else:
            action_id = int(xml_id)
        return Action(action_id).get_action_value()
Ejemplo n.º 27
0
class CreateSampleStart(metaclass=PoolMeta):
    __name__ = 'lims.create_sample.start'

    ind_required = fields.Function(fields.Boolean('Industry required'),
        'on_change_with_ind_required')
    equipment = fields.Many2One('lims.equipment', 'Equipment',
        domain=[('party', '=', Eval('party'))],
        states={'required': Bool(Eval('ind_required'))},
        context={'party': Eval('party')},
        depends=['party', 'ind_required'])
    component = fields.Many2One('lims.component', 'Component',
        domain=[('equipment', '=', Eval('equipment'))],
        states={'required': Bool(Eval('ind_required'))},
        depends=['equipment', 'ind_required'])
    comercial_product = fields.Many2One('lims.comercial.product',
        'Comercial Product', depends=['ind_required'],
        states={'required': Bool(Eval('ind_required'))})
    label = fields.Char('Label')
    ind_sampling_date = fields.Date('Sampling date')
    ind_volume = fields.Float('Received volume', depends=['ind_required'],
        states={'required': Bool(Eval('ind_required'))})
    sampling_type = fields.Many2One('lims.sampling.type',
        'Sampling Type', depends=['ind_required'],
        states={'required': Bool(Eval('ind_required'))})
    ind_operational_detail = fields.Text('Operational detail')
    ind_work_environment = fields.Text('Work environment')
    ind_analysis_reason = fields.Text('Reason for analysis')
    missing_data = fields.Boolean('Missing data')
    attributes_domain = fields.Function(fields.Many2Many(
        'lims.sample.attribute', None, None, 'Attributes domain'),
        'on_change_with_attributes_domain')
    sample_photo = fields.Binary('Sample Photo')
    label_photo = fields.Binary('Label Photo')
    oil_added = fields.Float('Liters Oil added')
    ind_equipment = fields.Integer('Equipment')
    ind_equipment_uom = fields.Selection([
        ('hs', 'Hs.'),
        ('km', 'Km.'),
        ], 'UoM', sort=False)
    ind_component = fields.Integer('Component')
    ind_component_uom = fields.Selection([
        ('hs', 'Hs.'),
        ('km', 'Km.'),
        ], 'UoM', sort=False)
    ind_oil = fields.Integer('Oil')
    ind_oil_uom = fields.Selection([
        ('hs', 'Hs.'),
        ('km', 'Km.'),
        ], 'UoM', sort=False)
    oil_changed = fields.Selection([
        (None, '-'),
        ('yes', 'Yes'),
        ('no', 'No'),
        ], 'Did change Oil?', sort=False)
    oil_filter_changed = fields.Selection([
        (None, '-'),
        ('yes', 'Yes'),
        ('no', 'No'),
        ], 'Did change Oil Filter?', sort=False)
    air_filter_changed = fields.Selection([
        (None, '-'),
        ('yes', 'Yes'),
        ('no', 'No'),
        ], 'Did change Air Filter?', sort=False)

    @classmethod
    def __setup__(cls):
        super().__setup__()
        for field in ('component', 'comercial_product'):
            cls.analysis_domain.on_change_with.add(field)
        cls.product_type.states['readonly'] = Bool(Eval('component'))
        if 'component' not in cls.product_type.depends:
            cls.product_type.depends.append('component')
        cls.matrix.states['readonly'] = Bool(Eval('comercial_product'))
        if 'comercial_product' not in cls.matrix.depends:
            cls.matrix.depends.append('comercial_product')
        cls.attributes.domain = [('id', 'in', Eval('attributes_domain'))]
        if 'attributes_domain' not in cls.attributes.depends:
            cls.attributes.depends.append('attributes_domain')
        cls.sample_client_description.required = False

    @staticmethod
    def default_ind_equipment_uom():
        return 'hs'

    @staticmethod
    def default_ind_component_uom():
        return 'hs'

    @staticmethod
    def default_ind_oil_uom():
        return 'hs'

    @fields.depends('fraction_type')
    def on_change_with_ind_required(self, name=None):
        Config = Pool().get('lims.configuration')
        if self.fraction_type:
            if self.fraction_type == Config(1).mcl_fraction_type:
                return True
        return False

    @fields.depends('equipment', 'component')
    def on_change_equipment(self):
        if not self.equipment and self.component:
            self.component = None

    @fields.depends('component')
    def on_change_component(self):
        if self.component:
            if self.component.product_type:
                self.product_type = self.component.product_type.id
            if self.component.comercial_product:
                self.comercial_product = self.component.comercial_product.id
                self.on_change_comercial_product()

    @fields.depends('comercial_product')
    def on_change_comercial_product(self):
        if self.comercial_product and self.comercial_product.matrix:
            self.matrix = self.comercial_product.matrix.id

    @fields.depends('product_type', 'component',
        '_parent_product_type.attribute_set')
    def on_change_with_attributes_domain(self, name=None):
        pool = Pool()
        SampleAttributeAttributeSet = pool.get(
            'lims.sample.attribute-attribute.set')
        attribute_set = None
        if self.product_type and self.product_type.attribute_set:
            attribute_set = self.product_type.attribute_set.id
        res = SampleAttributeAttributeSet.search([
            ('attribute_set', '=', attribute_set),
            ])
        return [x.attribute.id for x in res]

    @fields.depends('label')
    def on_change_label(self):
        self.labels = self.label

    @fields.depends('packages_quantity', 'package_type')
    def on_change_with_ind_volume(self, name=None):
        if (self.packages_quantity and
                self.package_type and self.package_type.capacity):
            return (self.packages_quantity * self.package_type.capacity)
        return None

    @fields.depends('fraction_type', 'sampling_type')
    def on_change_fraction_type(self):
        super().on_change_fraction_type()
        if not self.fraction_type:
            return
        if (not self.sampling_type and
                self.fraction_type.default_sampling_type):
            self.sampling_type = self.fraction_type.default_sampling_type
Ejemplo n.º 28
0
class Newborn(ModelSQL, ModelView):
    'Newborn Information'
    __name__ = 'gnuhealth.newborn'

    name = fields.Char('Newborn ID')
    patient = fields.Many2One('gnuhealth.patient',
                              'Baby',
                              required=True,
                              help="Patient associated to this newborn")

    mother = fields.Many2One('gnuhealth.patient', 'Mother')
    newborn_name = fields.Char('Name at Birth')
    birth_date = fields.DateTime('DoB',
                                 required=True,
                                 help="Date and Time of birth")
    photo = fields.Binary('Picture')
    newborn_sex = fields.Function(
        fields.Selection([
            ('m', 'Male'),
            ('f', 'Female'),
        ], 'Sex'), 'get_newborn_sex')

    # Sex / Gender at birth.

    sex = fields.Selection([
        ('m', 'Male'),
        ('f', 'Female'),
        ], 'Sex',sort=False, required=True,
            help="Sex at birth. It might differ from the current patient" \
            " sex")

    cephalic_perimeter = fields.Integer(
        'CP', help="Cephalic Perimeter in centimeters (cm)")
    length = fields.Integer('Length', help="Length in centimeters (cm)")
    weight = fields.Integer('Weight', help="Weight in grams (g)")
    apgar1 = fields.Integer('APGAR 1st minute')
    apgar5 = fields.Integer('APGAR 5th minute')
    apgar_scores = fields.One2Many('gnuhealth.neonatal.apgar', 'name',
                                   'APGAR scores')
    meconium = fields.Boolean('Meconium')
    congenital_diseases = fields.One2Many('gnuhealth.patient.disease',
                                          'newborn_id', 'Congenital diseases')
    reanimation_stimulation = fields.Boolean('Stimulation')
    reanimation_aspiration = fields.Boolean('Aspiration')
    reanimation_intubation = fields.Boolean('Intubation')
    reanimation_mask = fields.Boolean('Mask')
    reanimation_oxygen = fields.Boolean('Oxygen')
    test_vdrl = fields.Boolean('VDRL')
    test_toxo = fields.Boolean('Toxoplasmosis')
    test_chagas = fields.Boolean('Chagas')
    test_billirubin = fields.Boolean('Billirubin')
    test_audition = fields.Boolean('Audition')
    test_metabolic = fields.Boolean(
        'Metabolic ("heel stick screening")',
        help="Test for Fenilketonuria, Congenital Hypothyroidism, "
        "Quistic Fibrosis, Galactosemia")
    neonatal_ortolani = fields.Boolean('Positive Ortolani')
    neonatal_barlow = fields.Boolean('Positive Barlow')
    neonatal_hernia = fields.Boolean('Hernia')
    neonatal_ambiguous_genitalia = fields.Boolean('Ambiguous Genitalia')
    neonatal_erbs_palsy = fields.Boolean('Erbs Palsy')
    neonatal_hematoma = fields.Boolean('Hematomas')
    neonatal_talipes_equinovarus = fields.Boolean('Talipes Equinovarus')
    neonatal_polydactyly = fields.Boolean('Polydactyly')
    neonatal_syndactyly = fields.Boolean('Syndactyly')
    neonatal_moro_reflex = fields.Boolean('Moro Reflex')
    neonatal_grasp_reflex = fields.Boolean('Grasp Reflex')
    neonatal_stepping_reflex = fields.Boolean('Stepping Reflex')
    neonatal_babinski_reflex = fields.Boolean('Babinski Reflex')
    neonatal_blink_reflex = fields.Boolean('Blink Reflex')
    neonatal_sucking_reflex = fields.Boolean('Sucking Reflex')
    neonatal_swimming_reflex = fields.Boolean('Swimming Reflex')
    neonatal_tonic_neck_reflex = fields.Boolean('Tonic Neck Reflex')
    neonatal_rooting_reflex = fields.Boolean('Rooting Reflex')
    neonatal_palmar_crease = fields.Boolean('Transversal Palmar Crease')
    medication = fields.One2Many('gnuhealth.patient.medication', 'newborn_id',
                                 'Medication')
    responsible = fields.Many2One('gnuhealth.healthprofessional',
                                  'Doctor in charge',
                                  help="Signed by the health professional")
    dismissed = fields.DateTime('Discharged')
    bd = fields.Boolean('Stillbirth')
    died_at_delivery = fields.Boolean('Died at delivery room')
    died_at_the_hospital = fields.Boolean('Died at the hospital')
    died_being_transferred = fields.Boolean(
        'Died being transferred',
        help="The baby died being transferred to another health institution")
    tod = fields.DateTime('Time of Death')
    cod = fields.Many2One('gnuhealth.pathology', 'Cause of death')
    notes = fields.Text('Notes')

    @classmethod
    def __setup__(cls):
        super(Newborn, cls).__setup__()

        cls._sql_constraints = [
            ('name_uniq', 'unique(name)', 'The Newborn ID must be unique !'),
        ]

    def get_newborn_sex(self, name):
        if self.patient:
            return self.patient.sex
Ejemplo n.º 29
0
class ImportDataBinary(ModelSQL):
    "Import Data Binary"
    __name__ = 'test.import_data.binary'
    data = fields.Binary("Data")
Ejemplo n.º 30
0
class Attachment:
    __metaclass__ = PoolMeta
    __name__ = 'ir.attachment'

    cryptolog_signer = fields.Many2One('party.party', 'Cryptolog Signer',
        ondelete='RESTRICT', states={
            'readonly': Bool(Eval('cryptolog_status'))
            }, depends=['cryptolog_status']
        )
    cryptolog_id = fields.Char('Cryptolog ID', readonly=True)
    cryptolog_url = fields.Char('Cryptolog URL', readonly=True)
    cryptolog_status = fields.Selection([
        ('', ''),
        ('issued', 'Issued'),
        ('ready', 'Ready'),
        ('expired', 'Expired'),
        ('canceled', 'Canceled'),
        ('failed', 'Failed'),
        ('completed', 'Completed'),
        ], 'Cryptolog Status', readonly=True)
    cryptolog_data = fields.Function(
        fields.Binary('Signed Document',
            filename='name',
            states={
                'invisible': Eval('cryptolog_status') != 'completed'
            }, depends=['cryptolog_status']),
        'cryptolog_get_documents')
    cryptolog_logs = fields.Text('Cryptolog Logs', readonly=True)

    @classmethod
    def __setup__(cls):
        super(Attachment, cls).__setup__()
        cls._buttons.update({
                'cryptolog_request_transaction': {},
                'cryptolog_get_transaction_info': {}
                })

    @classmethod
    def cryptolog_headers(cls):
            return {'Content-Type': 'application/xml'}

    @classmethod
    def cryptolog_basic_auth(cls):
        assert (config.get(CONFIG_SECTION, 'auth_mode') == 'basic')
        username = config.get(CONFIG_SECTION, 'username')
        assert username
        password = config.get(CONFIG_SECTION, 'password')
        assert password
        return requests.auth.HTTPBasicAuth(username, password)

    def append_log(self, method, response):
        self.cryptolog_logs = self.cryptolog_logs or ''
        self.cryptolog_logs += '%s @ %s\n%s\n\n' % (method,
            datetime.datetime.utcnow(), response)

    @classmethod
    @ModelView.button
    def cryptolog_request_transaction(cls, attachments):
        # for now we support only one record
        attachment, = attachments
        url = config.get(CONFIG_SECTION, 'url')
        assert url
        verify = True
        if config.get(CONFIG_SECTION, 'no_verify') == '1':
            verify = False
        method = 'requester.requestTransaction'
        headers = cls.cryptolog_headers()
        auth = cls.cryptolog_basic_auth()
        data = {
            'documents': [{
                    'documentType': 'pdf',
                    'name': attachment.name,
                    'content': xmlrpclib.Binary(attachment.data)
                    }],
            'signers': [{
                    'lastname': attachment.cryptolog_signer.full_name,
                    'emailAddress': attachment.cryptolog_signer.email,
                    'phoneNum': attachment.cryptolog_signer.phone
                    }],
            'mustContactFirstSigner': True,
            'finalDocSent': True
            }

        successURL = config.get(CONFIG_SECTION, 'success-url')
        if successURL is not None:
            successURL = successURL.format(att=attachment)
            data['signers'][0]['successURL'] = successURL

        failURL = config.get(CONFIG_SECTION, 'fail-url')
        if failURL is not None:
            failURL = failURL.format(att=attachment)
            data['signers'][0]['failURL'] = failURL

        cancelURL = config.get(CONFIG_SECTION, 'cancel-url')
        if cancelURL is not None:
            cancelURL = cancelURL.format(att=attachment)
            data['signers'][0]['cancelURL'] = cancelURL

        data = xmlrpclib.dumps((data,), method)
        req = requests.post(url, headers=headers, auth=auth, data=data,
            verify=verify)
        if req.status_code > 299:
            raise Exception(req.content)
        response, _ = xmlrpclib.loads(req.content)
        attachment.cryptolog_status = 'issued'
        attachment.append_log(method, response)
        attachment.cryptolog_id = response[0]['id']
        attachment.cryptolog_url = response[0]['url']
        attachment.save()

    @classmethod
    @ModelView.button
    def cryptolog_get_transaction_info(cls, attachments):
        attachment, = attachments
        url = config.get(CONFIG_SECTION, 'url')
        assert url
        verify = True
        if config.get(CONFIG_SECTION, 'no_verify') == '1':
            verify = False
        method = 'requester.getTransactionInfo'
        headers = cls.cryptolog_headers()
        auth = cls.cryptolog_basic_auth()
        data = xmlrpclib.dumps((attachment.cryptolog_id,), method)
        req = requests.post(url, headers=headers, auth=auth, data=data,
            verify=verify)
        response, _ = xmlrpclib.loads(req.content)
        attachment.append_log(method, response)
        attachment.cryptolog_status = response[0]['status']
        attachment.save()

    def cryptolog_get_documents(self, name):
        # tryton trick (extra param on context to retrieve file size)
        if self.cryptolog_id and self.cryptolog_status == 'completed':
            if Transaction().context.get('%s.%s' % (self.__name__, name)) == \
                    'size':
                # does not make sense to retrieve the doc juste for the size
                return 1024
            url = config.get(CONFIG_SECTION, 'url')
            verify = True
            if config.get(CONFIG_SECTION, 'no_verify') == '1':
                verify = False
            method = 'requester.getDocuments'
            headers = self.cryptolog_headers()
            auth = self.cryptolog_basic_auth()
            data = xmlrpclib.dumps((self.cryptolog_id,), method)
            req = requests.post(url, headers=headers, auth=auth, data=data,
                verify=verify)
            response, _ = xmlrpclib.loads(req.content)
            return response[0][0]['content']