Example #1
0
class SyncJobQueue(models.Model):
    _name = "sync.job.queue"
    _order = "id asc"

    def _default_name(self):
        return str(uuid.uuid4())

    def _default_user(self):
        return self.env.user

    name = fields.Char('Name', default=_default_name)
    user_id = fields.Many2one('res.users', 'User', default=_default_user)
    active = fields.Boolean('Active', default=True)
    state = fields.Selection(
        (('P', u'Paused'), ('Q', u'Queued'), ('R', u'Running'), ('D', u'Done'),
         ('F', u'Failed')),
        default='P',
        string='State',
        help='Process State')
    type = fields.Selection((('1', u'Export'), ('2', u'Import')),
                            default='1',
                            string='State',
                            help='Type')
    source = fields.Reference([('res.users', 'User'),
                               ('res.partner', 'Contact'),
                               ('hr.payslip.run', 'Payslip Run')],
                              string='Source')
    method = fields.Char('Method')
    params = fields.Char('Params dict')
    date_processed = fields.Datetime('Date Processed')

    @api.model
    def sync_job_cron(self):
        _logger.info("Sync Job Cron - Started by cron")

        #how many jobs will I process?
        sync_job_qty = 1
        icp_obj = self.env['ir.config_parameter']
        try:
            sync_ob_qty = int(icp_obj.get_param('sync_job_qty'))
        except Exception as e:
            _logger.info(
                "Joberror no se encuentra config param sync_job_qty , %s",
                str(e))

        jobs = self.search([('state', '=', 'Q'), ('active', '=', True)],
                           order='id asc',
                           limit=sync_job_qty)
        for job in jobs:

            source = job.source
            method = job.method
            sync_method = getattr(source, method)
            try:
                job.state = 'R'
                res = sync_method(job.name, job.id)
                job.state = 'D'
            except Exception as e:
                _logger.info("Joberror Job name %s failed with message %s",
                             job.name, str(e))
                job.state = 'F'

        _logger.info("Sync Job Cron - Ended by cron")
Example #2
0
class Image(models.Model):
    _name = "base_multi_image.image"
    _order = "sequence, owner_model, owner_id, id"
    _sql_constraints = [
        ('uniq_name_owner', 'UNIQUE(owner_id, owner_model, name)',
         _('A document can have only one image with the same name.')),
    ]

    owner_id = fields.Integer(
        "Owner",
        required=True,
        ondelete="cascade",  # This Integer is really a split Many2one
    )
    owner_model = fields.Char(required=True)
    owner_ref_id = fields.Reference(
        selection="_selection_owner_ref_id",
        string="Referenced Owner",
        compute="_compute_owner_ref_id",
        store=True,
    )
    storage = fields.Selection([('url', 'URL'), ('file', 'OS file'),
                                ('db', 'Database'),
                                ('filestore', 'Filestore')],
                               required=True)
    name = fields.Char('Image title', translate=True)
    filename = fields.Char()
    extension = fields.Char('File extension', readonly=True)
    attachment_id = fields.Many2one('ir.attachment',
                                    string='Attachment',
                                    domain="[('index_content', '=', 'image')]")
    file_db_store = fields.Binary('Image stored in database',
                                  filters='*.png,*.jpg,*.gif')
    path = fields.Char("Image path", help="Image path")
    url = fields.Char('Image remote URL')
    image_main = fields.Binary("Full-sized image", compute="_get_image")
    image_medium = fields.Binary(
        "Medium-sized image",
        compute="_get_image_sizes",
        help="Medium-sized image. It is automatically resized as a "
        "128 x 128 px image, with aspect ratio preserved, only when the "
        "image exceeds one of those sizes. Use this field in form views "
        "or kanban views.")
    image_small = fields.Binary(
        "Small-sized image",
        compute="_get_image_sizes",
        help="Small-sized image. It is automatically resized as a 64 x 64 px "
        "image, with aspect ratio preserved. Use this field anywhere a "
        "small image is required.")
    comments = fields.Text('Comments', translate=True)
    sequence = fields.Integer(default=10)
    show_technical = fields.Boolean(compute="_show_technical")

    @api.model
    @tools.ormcache("self")
    def _selection_owner_ref_id(self):
        """Allow any model; after all, this field is readonly."""
        return [(r.model, r.name) for r in self.env["ir.model"].search([])]

    @api.multi
    @api.depends("owner_model", "owner_id")
    def _compute_owner_ref_id(self):
        """Get a reference field based on the split model and id fields."""
        for s in self:
            s.owner_ref_id = "{0.owner_model},{0.owner_id}".format(s)

    @api.multi
    @api.depends('storage', 'path', 'file_db_store', 'url')
    def _get_image(self):
        """Get image data from the right storage type."""
        for s in self:
            s.image_main = getattr(s, "_get_image_from_%s" % s.storage)()

    @api.multi
    @api.depends("owner_id", "owner_model")
    def _show_technical(self):
        """Know if you need to show the technical fields."""
        self.show_technical = all("default_owner_%s" %
                                  f not in self.env.context
                                  for f in ("id", "model"))

    @api.multi
    def _get_image_from_filestore(self):
        return self.attachment_id.datas

    @api.multi
    def _get_image_from_db(self):
        return self.file_db_store

    @api.multi
    def _get_image_from_file(self):
        if self.path and os.path.exists(self.path):
            try:
                with open(self.path, 'rb') as f:
                    return base64.b64encode(f.read())
            except Exception as e:
                _logger.error("Can not open the image %s, error : %s",
                              self.path,
                              e,
                              exc_info=True)
        else:
            _logger.error("The image %s doesn't exist ", self.path)

        return False

    @api.multi
    def _get_image_from_url(self):
        return self._get_image_from_url_cached(self.url)

    @api.model
    @tools.ormcache("url")
    def _get_image_from_url_cached(self, url):
        """Allow to download an image and cache it by its URL."""
        if url:
            try:
                (filename, header) = urllib.urlretrieve(url)
                with open(filename, 'rb') as f:
                    return base64.b64encode(f.read())
            except:
                _logger.error("URL %s cannot be fetched", url, exc_info=True)

        return False

    @api.multi
    @api.depends('image_main')
    def _get_image_sizes(self):
        for s in self:
            try:
                vals = tools.image_get_resized_images(
                    s.with_context(bin_size=False).image_main)
            except:
                vals = {"image_medium": False, "image_small": False}
            s.update(vals)

    @api.model
    def _make_name_pretty(self, name):
        return name.replace('_', ' ').capitalize()

    @api.onchange('url')
    def _onchange_url(self):
        if self.url:
            filename = self.url.split('/')[-1]
            self.name, self.extension = os.path.splitext(filename)
            self.name = self._make_name_pretty(self.name)

    @api.onchange('path')
    def _onchange_path(self):
        if self.path:
            self.name, self.extension = os.path.splitext(
                os.path.basename(self.path))
            self.name = self._make_name_pretty(self.name)

    @api.onchange('filename')
    def _onchange_filename(self):
        if self.filename:
            self.name, self.extension = os.path.splitext(self.filename)
            self.name = self._make_name_pretty(self.name)

    @api.onchange('attachment_id')
    def _onchange_attachmend_id(self):
        if self.attachment_id:
            self.name = self.attachment_id.res_name

    @api.constrains('storage', 'url')
    def _check_url(self):
        if self.storage == 'url' and not self.url:
            raise exceptions.ValidationError(
                _('You must provide an URL for the image.'))

    @api.constrains('storage', 'path')
    def _check_path(self):
        if self.storage == 'file' and not self.path:
            raise exceptions.ValidationError(
                _('You must provide a file path for the image.'))

    @api.constrains('storage', 'file_db_store')
    def _check_store(self):
        if self.storage == 'db' and not self.file_db_store:
            raise exceptions.ValidationError(
                _('You must provide an attached file for the image.'))

    @api.constrains('storage', 'attachment_id')
    def _check_attachment_id(self):
        if self.storage == 'filestore' and not self.attachment_id:
            raise exceptions.ValidationError(
                _('You must provide an attachment for the image.'))
Example #3
0
class QueueJobChunk(models.Model):
    _name = "queue.job.chunk"
    _description = "Queue Job Chunk"
    _inherit = "collection.base"

    @api.model
    def _selection_target_model(self):
        models = self.env["ir.model"].search([])
        return [(model.model, model.name) for model in models]

    @api.depends("model_name", "record_id")
    def _compute_reference(self):
        for rec in self:
            rec.company_id = self.env.user.company_id
            if rec.model_name and rec.record_id:
                rec.reference = "{},{}".format(rec.model_name, rec.record_id
                                               or 0)
                record = self.env[rec.model_name].browse(rec.record_id)
                if "company_id" in record._fields:
                    rec.company_id = record.company_id
            else:
                rec.reference = False

    # component fields
    usage = fields.Char("Usage")
    apply_on_model = fields.Char("Apply on model")

    data_str = fields.Text(string="Editable data")
    state = fields.Selection(
        [("pending", "Pending"), ("done", "Done"), ("fail", "Failed")],
        default="pending",
        string="State",
    )
    state_info = fields.Text("Additional state information")
    model_name = fields.Char("Model ID")
    record_id = fields.Integer("Record ID")
    reference = fields.Reference(
        string="Reference",
        selection="_selection_target_model",
        compute=_compute_reference,
        store=True,
    )
    company_id = fields.Many2one("res.company",
                                 compute=_compute_reference,
                                 store=True)

    @api.model_create_multi
    def create(self, vals):
        result = super().create(vals)
        for rec in result:
            rec.enqueue_job()
        return result

    def button_retry(self):
        self.enqueue_job()

    def enqueue_job(self):
        if DEBUG_MODE:
            return self.process_chunk()
        else:
            return self.with_delay().process_chunk()

    def process_chunk(self):
        self.ensure_one()
        usage = self.usage
        apply_on = self.apply_on_model
        with self.work_on(apply_on) as work:
            if DEBUG_MODE:
                with self.env.cr.savepoint():
                    processor = work.component(usage=usage)
                    result = processor.run()
                    self.state_info = ""
                    self.state = "done"
                    return result
            else:
                try:
                    with self.env.cr.savepoint():
                        processor = work.component(usage=usage)
                        result = processor.run()
                except Exception as e:
                    self.state = "fail"
                    self.state_info = type(e).__name__ + str(e.args)
                    return False
                self.state_info = ""
                self.state = "done"
                return result
Example #4
0
class PRTMailMessage(models.Model):
    _name = "mail.message"
    _inherit = "mail.message"

    author_display = fields.Char(string="Author", compute="_author_display")

    # Fields to avoid access check issues
    author_allowed_id = fields.Many2one(string="Author",
                                        comodel_name='res.partner',
                                        compute='_get_author_allowed',
                                        search='_search_author_allowed')

    partner_allowed_ids = fields.Many2many(string="Recipients",
                                           comodel_name='res.partner',
                                           compute='_get_partners_allowed')

    subject_display = fields.Char(string="Subject", compute="_subject_display")
    partner_count = fields.Integer(string="Recipients count",
                                   compute='_partner_count')
    record_ref = fields.Reference(string="Message Record",
                                  selection='_referenceable_models',
                                  compute='_record_ref')
    attachment_count = fields.Integer(string="Attachments count",
                                      compute='_attachment_count')
    thread_messages_count = fields.Integer(
        string="Messages in thread",
        compute='_thread_messages_count',
        help="Total number of messages in thread")
    ref_partner_ids = fields.Many2many(string="Followers",
                                       comodel_name='res.partner',
                                       compute='_message_followers')
    ref_partner_count = fields.Integer(string="Followers",
                                       compute='_ref_partner_count')

    # -- Count ref Partners
    @api.multi
    def _ref_partner_count(self):
        for rec in self:
            rec.ref_partner_count = len(rec.ref_partner_ids)

    """
    Sometimes user has access to record but does not have access to author or recipients.
    Below is a workaround for author, recipient and followers
    """
    # -- Get allowed author
    @api.depends('author_id')
    @api.multi
    def _get_author_allowed(self):
        for rec in self:
            author_id = rec.author_id
            try:
                author_id.check_access_rule('read')
                rec.author_allowed_id = author_id
            except:
                continue

# -- Get allowed recipients

    @api.depends('partner_ids')
    @api.multi
    def _get_partners_allowed(self):
        for rec in self:
            recipients_allowed = self.env['res.partner']
            for partner in rec.partner_ids:
                try:
                    partner.check_access_rule('read')
                    recipients_allowed += partner
                except:
                    continue
            rec.partner_allowed_ids = recipients_allowed

# -- Search allowed authors

    @api.model
    def _search_author_allowed(self, operator, value):
        return [('author_id', operator, value)]

# -- Get related record followers

    """
    Check if model has 'followers' field and user has access to followers
    """
    @api.depends('record_ref')
    @api.multi
    def _message_followers(self):
        for rec in self:
            if rec.record_ref:
                if 'message_partner_ids' in self.env[rec.model]._fields:
                    followers_allowed = self.env['res.partner']
                    for follower in rec.record_ref.message_partner_ids:
                        try:
                            follower.check_access_rule('read')
                            followers_allowed += follower
                        except:
                            continue
                    rec.ref_partner_ids = followers_allowed

# -- Dummy

    @api.multi
    def dummy(self):
        return

# -- Get Subject for tree view

    @api.depends('subject')
    @api.multi
    def _subject_display(self):

        # Get model names first. Use this method to get translated values
        ir_models = self.env['ir.model'].search([
            ('model', 'in', list(set(self.mapped('model'))))
        ])
        model_dict = {}
        for model in ir_models:
            # Check if model has "name" field
            has_name = self.env['ir.model.fields'].sudo().search_count([
                ('model_id', '=', model.id), ('name', '=', 'name')
            ])
            model_dict.update({model.model: [model.name, has_name]})

        # Compose subject
        for rec in self:
            if rec.subject:
                subject_display = rec.subject
            else:
                subject_display = '=== No Reference ==='

            # Has reference
            if rec.record_ref:
                subject_display = model_dict.get(rec.model)[0]

                # Has 'name' field
                if model_dict.get(rec.model, False)[1]:
                    subject_display = "%s: %s" % (subject_display,
                                                  rec.record_ref.sudo().name)

                # Has subject
                if rec.subject:
                    subject_display = "%s => %s" % (subject_display,
                                                    rec.subject)

            # Set subject
            rec.subject_display = subject_display

# -- Get Author for tree view

    @api.depends('author_allowed_id')
    @api.multi
    def _author_display(self):
        for rec in self:
            rec.author_display = rec.author_allowed_id.name if rec.author_allowed_id else rec.email_from

# -- Count recipients

    @api.depends('partner_allowed_ids')
    @api.multi
    def _partner_count(self):
        for rec in self:
            rec.partner_count = len(rec.partner_allowed_ids)

# -- Count attachments

    @api.depends('attachment_ids')
    @api.multi
    def _attachment_count(self):
        for rec in self:
            rec.attachment_count = len(rec.attachment_ids)

# -- Count messages in same thread

    @api.depends('res_id')
    @api.multi
    def _thread_messages_count(self):
        for rec in self:
            rec.thread_messages_count = self.search_count([
                '&', '&', ('model', '=', rec.model),
                ('res_id', '=', rec.res_id),
                ('message_type', '!=', 'notification')
            ])

# -- Ref models

    @api.model
    def _referenceable_models(self):
        # return [(x.model, x.name) for x in self.env['ir.model'].sudo().search([('model', '!=', 'mail.channel')])]
        """
        Mail channel is needed for legacy views (Settings->Technical Settings->Messages)
        """
        return [(x.model, x.name)
                for x in self.env['ir.model'].sudo().search([('transient', '=',
                                                              False)])]

# -- Compose reference

    @api.depends('res_id')
    @api.multi
    def _record_ref(self):
        for rec in self:
            if rec.model:
                if rec.res_id:
                    res = self.env[rec.model].sudo().search([("id", "=",
                                                              rec.res_id)])
                    if res:
                        rec.record_ref = res

# -- Open messages of the same thread

    @api.multi
    def thread_messages(self):
        self.ensure_one()

        global TREE_VIEW_ID
        global FORM_VIEW_ID

        # Cache Tree View and Form View ids
        if not TREE_VIEW_ID:
            TREE_VIEW_ID = self.env.ref(
                'prt_mail_messages.prt_mail_message_tree').id
            FORM_VIEW_ID = self.env.ref(
                'prt_mail_messages.prt_mail_message_form').id

        return {
            'name':
            _("Messages"),
            "views": [[TREE_VIEW_ID, "tree"], [FORM_VIEW_ID, "form"]],
            'res_model':
            'mail.message',
            'type':
            'ir.actions.act_window',
            'target':
            'current',
            'domain': [('message_type', '!=', 'notification'),
                       ('model', '=', self.model),
                       ('res_id', '=', self.res_id)]
        }

# -- Override _search

    """
    mail.message overrides generic '_search' defined in 'model' to implement own logic for message access rights.
    However sometimes it does not work as expected.
    So we use generic method in 'model' and check access rights later in 'search' method.
    Following keys in context are used:
        - 'check_messages_access': if not set legacy 'search' is performed
    """
    @api.model
    def _search(self,
                args,
                offset=0,
                limit=None,
                order=None,
                count=False,
                access_rights_uid=None):

        if not self._context.get('check_messages_access', False):
            return super(PRTMailMessage,
                         self)._search(args=args,
                                       offset=offset,
                                       limit=limit,
                                       order=order,
                                       count=count,
                                       access_rights_uid=access_rights_uid)

        if expression.is_false(self, args):
            # optimization: no need to query, as no record satisfies the domain
            return 0 if count else []

        query = self._where_calc(args)
        order_by = self._generate_order_by(order, query)
        from_clause, where_clause, where_clause_params = query.get_sql()

        where_str = where_clause and (" WHERE %s" % where_clause) or ''

        if count:
            # Ignore order, limit and offset when just counting, they don't make sense and could
            # hurt performance
            query_str = 'SELECT count(1) FROM ' + from_clause + where_str
            self._cr.execute(query_str, where_clause_params)
            res = self._cr.fetchone()
            return res[0]

        limit_str = limit and ' limit %d' % limit or ''
        offset_str = offset and ' offset %d' % offset or ''
        query_str = 'SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str
        self._cr.execute(query_str, where_clause_params)
        res = self._cr.fetchall()

        # TDE note: with auto_join, we could have several lines about the same result
        # i.e. a lead with several unread messages; we uniquify the result using
        # a fast way to do it while preserving order (http://www.peterbe.com/plog/uniqifiers-benchmark)
        def _uniquify_list(seq):
            seen = set()
            return [x for x in seq if x not in seen and not seen.add(x)]

        return _uniquify_list([x[0] for x in res])

# -- Override read

    """
    Avoid access rights check implemented in original mail.message
    Will check them later in "search"
    Using base model function instead
        Following keys in context are used:
        - 'check_messages_access': if not set legacy 'search' is performed
    """
    @api.multi
    def read(self, fields=None, load='_classic_read'):

        if not self._context.get('check_messages_access', False):
            return super(PRTMailMessage, self).read(fields=fields, load=load)
        """
        From here starts the original 'read' code
        """
        # split fields into stored and computed fields
        stored, inherited, computed = [], [], []
        for name in fields:
            field = self._fields.get(name)
            if field:
                if field.store:
                    stored.append(name)
                elif field.base_field.store:
                    inherited.append(name)
                else:
                    computed.append(name)
            else:
                _logger.warning("%s.read() with unknown field '%s'",
                                self._name, name)

        # fetch stored fields from the database to the cache; this should feed
        # the prefetching of secondary records
        self._read_from_database(stored, inherited)

        # retrieve results from records; this takes values from the cache and
        # computes remaining fields
        result = []
        name_fields = [(name, self._fields[name])
                       for name in (stored + inherited + computed)]
        use_name_get = (load == '_classic_read')

        for record in self:
            try:
                values = {'id': record.id}
                for name, field in name_fields:
                    values[name] = field.convert_to_read(
                        record[name], record, use_name_get)
                result.append(values)
            except MissingError:
                pass

        return result

# -- Override Search

    """
    Mail message access rights/rules checked must be done based on the access rights/rules of the message record.
    As a workaround we are using 'search' method to filter messages from unavailable records.
    
    Display only messages where user has read access to related record.
    
    Following keys in context are used:
    - 'check_messages_access': if not set legacy 'search' is performed
    - 'force_record_reset': in case message refers to non-existing (e.g. removed) record model and res_id will be set NULL
    """
    @api.model
    def search(self, args, offset=0, limit=None, order=None, count=False):

        if not self._context.get('check_messages_access', False):
            return super(PRTMailMessage, self).search(args=args,
                                                      offset=offset,
                                                      limit=limit,
                                                      order=order,
                                                      count=count)

        # Store context keys
        force_record_reset = self._context.get('force_record_reset', False)
        # Store initial args in case we need them later
        modded_args = args
        sort_order = 'desc' if order == 'date DESC, id DESC' else 'asc'

        # New query
        # Check model access 1st
        # Add mail.channel by default because we are not showing chat/channel messages
        forbidden_models = ['mail.channel']

        # Get list of possible followed models
        self._cr.execute(""" SELECT model FROM ir_model mdl
                                WHERE mdl.is_mail_thread = True 
                                AND name != 'mail.channel' """)

        # Check each model
        for msg_model in self._cr.fetchall():
            if not self.env['ir.model.access'].check(
                    msg_model[0], 'read', raise_exception=False):
                forbidden_models.append(msg_model[0])

        # Add forbidden models condition to domain
        if len(forbidden_models) > 0:
            modded_args.append(['model', 'not in', forbidden_models])

        # Return Count
        if count:
            return super(PRTMailMessage, self).search(args=modded_args,
                                                      offset=offset,
                                                      limit=limit,
                                                      order=order,
                                                      count=True)

        # Get records
        res_ids = self._search(args=modded_args,
                               offset=offset,
                               limit=limit,
                               order=order,
                               count=False)
        res = self.browse(res_ids)
        # Now check record rules
        res_allowed = self.env['mail.message']
        len_initial = limit if limit else len(res)
        len_filtered = 0
        last_val = False

        # Check records
        """
        Check in we need include "lost" messages. These are messages with no model or res_id
        """
        get_lost = self._context.get('get_lost', False)

        for rec in res:
            last_val = rec.date

            # No model
            if not rec.model:
                if get_lost:
                    res_allowed += rec
                    len_filtered += 1
                continue

            # No id
            if not rec.res_id:
                if get_lost:
                    res_allowed += rec
                    len_filtered += 1
                continue

            # Check access rules on record. Skip if refers to deleted record
            try:
                target_rec = self.env[rec.model].search([('id', '=',
                                                          rec.res_id)])
                if not target_rec:
                    # Reset model and res_id
                    if force_record_reset:
                        rec.sudo().write({'model': False, 'res_id': False})
                    continue
                # Check message record
                target_rec.check_access_rule('read')
            except:
                continue

            res_allowed += rec
            len_filtered += 1

        # We store ids of the record with the last date not to fetch them twice while fetching more recs
        last_ids = res.filtered(lambda d: d.date == last_val).ids

        del res  # Hope Python will free memory asap!))

        # Return if initially got less then limit
        if limit is None or len_initial < limit:
            return res_allowed

        len_remaining = len_initial - len_filtered

        # Return if all allowed
        if len_remaining == 0:
            return res_allowed

        # Get remaining recs
        while len_remaining > 0:
            if sort_order == 'desc':
                new_args = modded_args + [['date', '<=', last_val],
                                          ['id', 'not in', last_ids]]
            else:
                new_args = modded_args + [['date', '>=', last_val],
                                          ['id', 'not in', last_ids]]

            # Let's try!))
            res_2_ids = self._search(args=new_args,
                                     offset=0,
                                     limit=len_remaining,
                                     order=order,
                                     count=False)
            res_2 = self.browse(res_2_ids)

            if len(res_2) < 1:
                break

            # Check records
            for rec in res_2:
                last_val = rec.date

                # No model
                if not rec.model:
                    if get_lost:
                        res_allowed += rec
                        len_filtered += 1
                    continue

                # No res_id
                if not rec.res_id:
                    if get_lost:
                        res_allowed += rec
                        len_filtered += 1
                    continue

                # Check access rules on record. Skip if refers to deleted record
                try:
                    target_rec = self.env[rec.model].search([('id', '=',
                                                              rec.res_id)])
                    if not target_rec:
                        # Reset model and res_id
                        if force_record_reset:
                            rec.sudo().write({'model': False, 'res_id': False})
                        continue
                    # Check message record
                    target_rec.check_access_rule('read')
                except:
                    continue

                res_allowed += rec
                len_remaining -= 1

            if not len_remaining == 0:
                last_ids += res_2.filtered(lambda d: d.date == last_val).ids

        return res_allowed

# -- Prepare context for reply or quote message

    @api.multi
    def reply_prep_context(self):
        self.ensure_one()
        body = False
        wizard_mode = self._context.get('wizard_mode', False)

        if wizard_mode in ['quote', 'forward']:
            body = (_(
                "<div font-style=normal;><br/></div><blockquote>----- Original message ----- <br/> Date: %s <br/> From: %s <br/> Subject: %s <br/><br/>%s</blockquote>"
            ) % (str(self.date), self.author_display, self.subject_display,
                 self.body))

        ctx = {
            'default_res_id':
            self.res_id,
            'default_parent_id':
            False if wizard_mode == 'forward' else self.id,
            'default_model':
            self.model,
            'default_partner_ids':
            [self.author_allowed_id.id] if self.author_allowed_id else [],
            'default_attachment_ids':
            self.attachment_ids.ids if wizard_mode == 'forward' else [],
            'default_is_log':
            False,
            'default_body':
            body,
            'default_wizard_mode':
            wizard_mode
        }
        return ctx

# -- Reply or quote message

    @api.multi
    def reply(self):
        self.ensure_one()

        return {
            'name': _("New message"),
            "views": [[False, "form"]],
            'res_model': 'mail.compose.message',
            'type': 'ir.actions.act_window',
            'target': 'new',
            'context': self.reply_prep_context()
        }

# -- Move message

    @api.multi
    def move(self):
        self.ensure_one()

        return {
            'name': _("Move messages"),
            "views": [[False, "form"]],
            'res_model': 'prt.message.move.wiz',
            'type': 'ir.actions.act_window',
            'target': 'new'
        }
Example #5
0
class IrServerObjectLines(models.Model):
    _name = 'ir.server.object.lines'
    _description = 'Server Action value mapping'
    _sequence = 'ir_actions_id_seq'

    server_id = fields.Many2one('ir.actions.server',
                                string='Related Server Action',
                                ondelete='cascade')
    col1 = fields.Many2one('ir.model.fields', string='Field', required=True)
    value = fields.Text(
        required=True,
        help="Expression containing a value specification. \n"
        "When Formula type is selected, this field may be a Python expression "
        " that can use the same values as for the code field on the server action.\n"
        "If Value type is selected, the value will be used directly without evaluation."
    )
    type = fields.Selection([('value', 'Value'), ('reference', 'Reference'),
                             ('equation', 'Python expression')],
                            'Evaluation Type',
                            default='value',
                            required=True,
                            change_default=True)
    resource_ref = fields.Reference(string='Record',
                                    selection='_selection_target_model',
                                    compute='_compute_resource_ref',
                                    inverse='_set_resource_ref')

    @api.model
    def _selection_target_model(self):
        models = self.env['ir.model'].search([])
        return [(model.model, model.name) for model in models]

    @api.depends('col1.relation', 'value', 'type')
    def _compute_resource_ref(self):
        for line in self:
            if line.type in ['reference', 'value'
                             ] and line.col1 and line.col1.relation:
                value = line.value or ''
                try:
                    value = int(value)
                    if not self.env[line.col1.relation].browse(value).exists():
                        record = self.env[line.col1.relation]._search([],
                                                                      limit=1)
                        value = record[0] if record else 0
                except ValueError:
                    record = self.env[line.col1.relation]._search([], limit=1)
                    value = record[0] if record else 0
                line.resource_ref = '%s,%s' % (line.col1.relation, value)
            else:
                line.resource_ref = False

    @api.onchange('resource_ref')
    def _set_resource_ref(self):
        for line in self.filtered(lambda line: line.type == 'reference'):
            if line.resource_ref:
                line.value = str(line.resource_ref.id)

    @api.multi
    def eval_value(self, eval_context=None):
        result = dict.fromkeys(self.ids, False)
        for line in self:
            expr = line.value
            if line.type == 'equation':
                expr = safe_eval(line.value, eval_context)
            elif line.col1.ttype in ['many2one', 'integer']:
                try:
                    expr = int(line.value)
                except Exception:
                    pass
            result[line.id] = expr
        return result
Example #6
0
class ThemeView(models.Model):
    _name = 'theme.ir.ui.view'
    _description = 'Theme UI View'

    def compute_arch_fs(self):
        path_info = get_resource_from_path(self._context['install_filename'])
        if path_info:
            return '/'.join(path_info[0:2])

    name = fields.Char(required=True)
    key = fields.Char()
    type = fields.Char()
    priority = fields.Integer(default=16, required=True)
    mode = fields.Selection([('primary', "Base view"),
                             ('extension', "Extension View")])
    active = fields.Boolean(default=True)
    arch = fields.Text(translate=xml_translate)
    arch_fs = fields.Char(default=compute_arch_fs)
    inherit_id = fields.Reference(
        selection=[('ir.ui.view',
                    'ir.ui.view'), ('theme.ir.ui.view', 'theme.ir.ui.view')])
    copy_ids = fields.One2many('ir.ui.view',
                               'theme_template_id',
                               'Views using a copy of me',
                               copy=False,
                               readonly=True)

    # TODO master add missing field: customize_show

    @api.multi
    def _convert_to_base_model(self, website, **kwargs):
        self.ensure_one()
        inherit = self.inherit_id
        if self.inherit_id and self.inherit_id._name == 'theme.ir.ui.view':
            inherit = self.inherit_id.with_context(
                active_test=False).copy_ids.filtered(
                    lambda x: x.website_id == website)
            if not inherit:
                # inherit_id not yet created, add to the queue
                return False

        if inherit and inherit.website_id != website:
            website_specific_inherit = self.env['ir.ui.view'].with_context(
                active_test=False).search([('key', '=', inherit.key),
                                           ('website_id', '=', website.id)],
                                          limit=1)
            if website_specific_inherit:
                inherit = website_specific_inherit

        new_view = {
            'type': self.type or 'qweb',
            'name': self.name,
            'arch': self.arch,
            'key': self.key,
            'inherit_id': inherit and inherit.id,
            'arch_fs': self.arch_fs,
            'priority': self.priority,
            'active': self.active,
            'theme_template_id': self.id,
            'website_id': website.id,
        }

        if self.mode:  # if not provided, it will be computed automatically (if inherit_id or not)
            new_view['mode'] = self.mode

        return new_view
Example #7
0
class CouponPromotion(models.Model):
    """
    Promotions with coupons
    """

    _name = "coupon.promotion"
    _description = __doc__

    @api.multi
    @api.depends("name")
    def _get_number(self):
        for row in self:
            number = ""
            if row.name:
                number = row.name.split("-")[-1:][0]
            row.number = number
        return True

    name = fields.Char(_("Code"))
    number = fields.Char(_("Number"), compute="_get_number", store=True)
    partner_id = fields.Many2one("res.partner", _("Customer"))
    partner_name = fields.Char(_("Customer Name"), required=True)
    partner_street = fields.Char(_("Customer Address"), required=True)
    partner_phone = fields.Char(_("Customer Phone"), required=True)
    type_identifier = fields.Selection(
        [
            ("04", "RUC"),
            ("05", "CEDULA"),
            ("06", "PASAPORTE"),
            ("07", "CONSUMIDOR FINAL"),
        ],
        string="Tipo ID",
        default="06",
        required=True,
    )
    identification = fields.Char(_("Identification"), required=True)
    email = fields.Char(_("Email"), required=True)
    terms = fields.Boolean(_("Accept Terms and Conditions"), required=True)
    coupon_id = fields.Many2one("coupon", _("Coupon"))
    send = fields.Boolean("Email Send", default=False)
    currency_id = fields.Many2one("res.currency",
                                  related="coupon_id.currency_id",
                                  store=True)
    date_from = fields.Date(_("From"),
                            related="coupon_id.date_from",
                            store=True)
    date_to = fields.Date(_("To"), related="coupon_id.date_to", store=True)
    date_used = fields.Date(_("Date Used"))
    team_ids = fields.Many2many(
        "crm.team",
        "coupon_promotion_crm_team_rel",
        "coupon_id",
        "team_id",
        _("Sale Channels"),
        related="coupon_id.team_ids",
        store=True,
    )
    value = fields.Monetary(_("Amount"))
    used = fields.Boolean(_("Used"))
    used_in = fields.Char(_("Used in"))
    reference = fields.Reference(string="Reference",
                                 selection="_get_reference_models")
    state = fields.Selection(
        [("draft", _("Draft")), ("confirm", _("Confirm")),
         ("cancel", _("Cancel"))],
        string=_("State"),
        related="coupon_id.state",
        store=True,
    )

    @api.multi
    def _get_reference_models(self):
        refs = self.env["ir.model"].search([
            "|", ("model", "=", "account.invoice"), ("model", "=", "pos.order")
        ])
        return [(ref.model, ref.name) for ref in refs] + [("", "")]

    @api.multi
    def unlink(self):
        for row in self:
            if row.state != "draft":
                raise UserError(
                    _("You cannot delete an Coupon which is not draft or cancelled"
                      ))
        return super(Coupon, self).unlink()

    @api.model
    def fields_view_get(self,
                        view_id=None,
                        view_type="form",
                        toolbar=False,
                        submenu=False):
        res = models.Model.fields_view_get(self,
                                           view_id=view_id,
                                           view_type=view_type,
                                           toolbar=toolbar,
                                           submenu=submenu)
        update = view_type in ["form", "tree"]
        if update:
            doc = etree.XML(res["arch"])
            for t in doc.xpath("//" + view_type):
                t.attrib["create"] = "false"
                t.attrib["edit"] = "false"
            res["arch"] = etree.tostring(doc)
        return res

    @api.model
    def create(self, vals):
        today = fields.Date.context_today(self)
        partner_obj = self.env["res.partner"]
        coupon_obj = self.env["coupon"]
        promotion_obj = self.env["coupon.promotion"]
        res = self
        if self.env.context.get("website_id"):
            partner_id = partner_obj.search([("identification", "=",
                                              vals.get("identification"))])
            if not partner_id:
                partner_id = partner_obj.create({
                    "name":
                    vals.get("partner_name"),
                    "type_identifier":
                    vals.get("type_identifier"),
                    "identification":
                    vals.get("identification"),
                    "email":
                    vals.get("email"),
                    "phone":
                    vals.get("partner_phone"),
                    "street":
                    vals.get("partner_street"),
                    "customer":
                    True,
                })
            coupon_id = coupon_obj.search([
                ("date_from", "<=", today),
                ("date_to", ">=", today),
                ("coupon_left", ">", 0),
                ("state", "=", "confirm"),
            ])
            last_coupon = partner_coupons = promotion_obj.search(
                [("partner_id", "=", partner_id.id)], limit=1)
            if not coupon_id:
                return last_coupon
            number = len(coupon_id.mapped("coupon_ids")) + 1
            partner_coupons = promotion_obj.search([
                ("partner_id", "=", partner_id.id),
                ("coupon_id", "=", coupon_id.id)
            ])
            if len(partner_coupons) < coupon_id.coupon_partner:
                vals.update({
                    "name":
                    "{}-{}-{:0>4}".format(coupon_id.code,
                                          partner_id.identification[-4:],
                                          number),
                    "coupon_id":
                    coupon_id.id,
                    "partner_id":
                    partner_id.id,
                    "value":
                    coupon_id.coupon_value,
                })
                res = super(CouponPromotion, self).create(vals)
                self.coupon_notify("ok", res)
            else:
                res = last_coupon
                self.coupon_notify("fail", res)
        return res

    @api.model
    def coupon_notify(self, type, coupon_ids):
        for row in coupon_ids:
            mail_obj = self.env["mail.mail"]
            ean = BytesIO()
            generate("code128",
                     u"{}".format(row.name),
                     writer=ImageWriter(),
                     output=ean)
            ean.seek(0)
            jpgdata = ean.read()
            imgdata = base64.encodestring(jpgdata)
            variables = {
                "name":
                row.coupon_id.name,
                "partner":
                row.partner_id.name_get()[0][1],
                "date_to":
                row.coupon_id.date_to,
                "code":
                row.name,
                "barcode":
                '<img src="data:image/jpeg;base64,{}" />'.format(
                    imgdata.decode("utf-8")),
                "amount":
                row.value,
                "min_amount":
                row.coupon_id.min_amount,
                "coupon_limit":
                row.coupon_id.coupon_partner,
            }
            if type == "ok":
                template = Template(
                    row.coupon_id.body_html,
                    trim_blocks=True,
                    lstrip_blocks=True,
                    autoescape=True,
                )
            elif type == "fail":
                template = Template(
                    row.coupon_id.body_html_limit,
                    trim_blocks=True,
                    lstrip_blocks=True,
                    autoescape=True,
                )
            vals = {
                "state":
                "outgoing",
                "subject":
                _("{}: {}".format(self.env.user.company_id.name_get()[0][1],
                                  row.coupon_id.name)),
                "body_html":
                template.render(**variables),
                "email_to":
                row.email,
                "email_from":
                "{} <{}>".format(row.env.user.company_id.name,
                                 row.env.user.company_id.email),
                "author_id":
                row.env.user.company_id.partner_id.id,
            }
            mail = mail_obj.create(vals)
            mail.send()
            row.send = True
        return True

    @api.model
    def validate_coupon(self, coupon, partner_id, crm_team_id, due):
        def _validate(coupon):
            today = fields.Date.context_today(self)
            return {
                coupon.coupon_id.min_amount > due:
                "Valor minimo para aplicar el cupón es de {}".format(
                    coupon.coupon_id.min_amount),
                not coupon.date_from <= today <= coupon.date_to:
                "Cupón expirado",
                coupon.used:
                "Cupon aplicado",
                coupon.partner_id.id != partner_id:
                "El cupón a aplicar no corresponde al cliente a facturar",
            }.get(True) or False

        coupon = self.search(
            [
                ("name", "=", coupon),
                ("coupon_id.coupon_apply", "in", ("both", "pos")),
                ("team_ids.id", "=", crm_team_id),
                ("state", "=", "confirm"),
            ],
            limit=1,
        )
        if not coupon.id:
            return "Cupón no valido", False
        return _validate(coupon), coupon.value
Example #8
0
class File(models.Model):

    _name = "dms.file"
    _description = "File"

    _inherit = [
        "portal.mixin",
        "dms.security.mixin",
        "dms.mixins.thumbnail",
        "mail.thread",
        "mail.activity.mixin",
    ]

    _order = "name asc"

    # ----------------------------------------------------------
    # Database
    # ----------------------------------------------------------

    name = fields.Char(string="Filename", required=True, index=True)

    active = fields.Boolean(
        string="Archived",
        default=True,
        help="If a file is set to archived, it is not displayed, but still exists.",
    )

    directory_id = fields.Many2one(
        comodel_name="dms.directory",
        string="Directory",
        domain="[('permission_create', '=', True)]",
        context="{'dms_directory_show_path': True}",
        ondelete="restrict",
        auto_join=True,
        required=True,
        index=True,
    )

    storage_id = fields.Many2one(
        related="directory_id.storage_id",
        comodel_name="dms.storage",
        string="Storage",
        auto_join=True,
        readonly=True,
        store=True,
    )

    is_hidden = fields.Boolean(
        string="Storage is Hidden", related="storage_id.is_hidden", readonly=True
    )

    company_id = fields.Many2one(
        related="storage_id.company_id",
        comodel_name="res.company",
        string="Company",
        readonly=True,
        store=True,
        index=True,
    )

    path_names = fields.Char(
        compute="_compute_path", string="Path Names", readonly=True, store=False
    )

    path_json = fields.Text(
        compute="_compute_path", string="Path Json", readonly=True, store=False
    )

    color = fields.Integer(string="Color", default=0)

    category_id = fields.Many2one(
        comodel_name="dms.category",
        context="{'dms_category_show_path': True}",
        string="Category",
    )

    tag_ids = fields.Many2many(
        comodel_name="dms.tag",
        relation="dms_file_tag_rel",
        column1="fid",
        column2="tid",
        string="Tags",
    )

    content = fields.Binary(
        compute="_compute_content",
        inverse="_inverse_content",
        string="Content",
        attachment=False,
        prefetch=False,
        required=True,
        store=False,
    )

    extension = fields.Char(
        compute="_compute_extension", string="Extension", readonly=True, store=True
    )

    res_mimetype = fields.Char(
        compute="_compute_mimetype", string="Type", readonly=True, store=True
    )

    size = fields.Integer(string="Size", readonly=True)

    checksum = fields.Char(string="Checksum/SHA1", readonly=True, size=40, index=True)

    content_binary = fields.Binary(
        string="Content Binary", attachment=False, prefetch=False, invisible=True
    )

    save_type = fields.Char(
        compute="_compute_save_type",
        string="Current Save Type",
        invisible=True,
        prefetch=False,
    )

    migration = fields.Char(
        compute="_compute_migration",
        string="Migration Status",
        readonly=True,
        prefetch=False,
        compute_sudo=True,
    )
    require_migration = fields.Boolean(
        compute="_compute_migration", store=True, compute_sudo=True,
    )

    content_file = fields.Binary(
        attachment=True, string="Content File", prefetch=False, invisible=True
    )

    def check_access_token(self, access_token=False):
        res = False
        if access_token:
            if self.access_token and self.access_token == access_token:
                return True
            else:
                items = (
                    self.env["dms.directory"]
                    .sudo()
                    .search([("access_token", "=", access_token)])
                )
                if items:
                    item = items[0]
                    if self.directory_id.id == item.id:
                        return True
                    else:
                        directory_item = self.directory_id
                        while directory_item.parent_id:
                            if directory_item.id == self.directory_id.id:
                                return True
                            directory_item = directory_item.parent_id
                        # Fix last level
                        if directory_item.id == self.directory_id.id:
                            return True
        return res

    attachment_id = fields.Many2one(
        comodel_name="ir.attachment",
        string="Attachment File",
        prefetch=False,
        invisible=True,
        ondelete="cascade",
    )
    res_model = fields.Char(string="Linked attachments model")
    res_id = fields.Integer(string="Linked attachments record ID")
    record_ref = fields.Reference(
        string="Record Referenced",
        compute="_compute_record_ref",
        selection=[],
        readonly=True,
        store=False,
    )
    storage_id_save_type = fields.Selection(related="storage_id.save_type", store=False)

    @api.depends("res_model", "res_id")
    def _compute_record_ref(self):
        for record in self:
            record.record_ref = False
            if record.res_model and record.res_id:
                record.record_ref = "{},{}".format(record.res_model, record.res_id)

    def get_human_size(self):
        return human_size(self.size)

    def _get_share_url(self, redirect=False, signup_partner=False, pid=None):
        self.ensure_one()
        return "/my/dms/file/{}/download?access_token={}&db={}".format(
            self.id, self._portal_ensure_token(), self.env.cr.dbname,
        )

    # ----------------------------------------------------------
    # Helper
    # ----------------------------------------------------------

    @api.model
    def _get_checksum(self, binary):
        return hashlib.sha1(binary or b"").hexdigest()

    @api.model
    def _get_content_inital_vals(self):
        return {"content_binary": False, "content_file": False}

    def _update_content_vals(self, vals, binary):
        new_vals = vals.copy()
        new_vals.update(
            {
                "checksum": self._get_checksum(binary),
                "size": binary and len(binary) or 0,
            }
        )
        if self.storage_id.save_type in ["file", "attachment"]:
            new_vals["content_file"] = self.content
        else:
            new_vals["content_binary"] = self.content and binary
        return new_vals

    @api.model
    def _get_binary_max_size(self):
        return int(
            self.env["ir.config_parameter"]
            .sudo()
            .get_param("dms.binary_max_size", default=25)
        )

    @api.model
    def _get_forbidden_extensions(self):
        get_param = self.env["ir.config_parameter"].sudo().get_param
        extensions = get_param("dms.forbidden_extensions", default="")
        return [extension.strip() for extension in extensions.split(",")]

    def _get_thumbnail_placeholder_name(self):
        return self.extension and "file_%s.svg" % self.extension or ""

    # ----------------------------------------------------------
    # Actions
    # ----------------------------------------------------------

    def action_migrate(self, logging=True):
        record_count = len(self)
        index = 1
        for dms_file in self:
            if logging:
                info = (index, record_count, dms_file.migration)
                _logger.info(_("Migrate File %s of %s [ %s ]") % info)
                index += 1
            dms_file.write({"content": dms_file.with_context({}).content})

    def action_save_onboarding_file_step(self):
        self.env.user.company_id.set_onboarding_step_done(
            "documents_onboarding_file_state"
        )

    # ----------------------------------------------------------
    # SearchPanel
    # ----------------------------------------------------------

    @api.model
    def _search_panel_directory(self, **kwargs):
        search_domain = (kwargs.get("search_domain", []),)
        category_domain = kwargs.get("category_domain", [])
        if category_domain and len(category_domain):
            return "=", category_domain[0][2]
        if search_domain and len(search_domain):
            for domain in search_domain[0]:
                if domain[0] == "directory_id":
                    return domain[1], domain[2]
        return None, None

    @api.model
    def _search_panel_domain(self, field, operator, directory_id, comodel_domain=False):
        if not comodel_domain:
            comodel_domain = []
        files_ids = self.search([("directory_id", operator, directory_id)]).ids
        return expression.AND([comodel_domain, [(field, "in", files_ids)]])

    @api.model
    def search_panel_select_range(self, field_name, **kwargs):
        operator, directory_id = self._search_panel_directory(**kwargs)
        if directory_id and field_name == "directory_id":
            domain = [("parent_id", operator, directory_id)]
            values = (
                self.env["dms.directory"]
                .with_context(directory_short_name=True)
                .search_read(domain, ["display_name", "parent_id"])
            )
            return {
                "parent_field": "parent_id",
                "values": values if len(values) > 1 else [],
            }
        context = {}
        if field_name == "directory_id":
            context["directory_short_name"] = True
        return super(File, self.with_context(**context)).search_panel_select_range(
            field_name, **kwargs
        )

    @api.model
    def search_panel_select_multi_range(self, field_name, **kwargs):
        operator, directory_id = self._search_panel_directory(**kwargs)
        if field_name == "tag_ids":
            sql_query = """
                SELECT t.name AS name, t.id AS id, c.name AS group_name,
                    c.id AS group_id, COUNT(r.fid) AS count
                FROM dms_tag t
                JOIN dms_category c ON t.category_id = c.id
                LEFT JOIN dms_file_tag_rel r ON t.id = r.tid
                {directory_where_clause}
                GROUP BY c.name, c.id, t.name, t.id
                ORDER BY c.name, c.id, t.name, t.id;
            """
            where_clause = ""
            params = []
            if directory_id:
                file_ids = self.search([("directory_id", operator, directory_id)]).ids
                if file_ids:
                    where_clause = "WHERE r.fid in %s"
                    params.append(tuple(file_ids))
                else:
                    where_clause = "WHERE 1 = 0"
            # pylint: disable=sql-injection
            final_query = sql_query.format(directory_where_clause=where_clause)
            self.env.cr.execute(final_query, params)
            return self.env.cr.dictfetchall()
        if directory_id and field_name in ["directory_id", "category_id"]:
            comodel_domain = kwargs.pop("comodel_domain", [])
            directory_comodel_domain = self._search_panel_domain(
                "file_ids", operator, directory_id, comodel_domain
            )
            return super(
                File, self.with_context(directory_short_name=True)
            ).search_panel_select_multi_range(
                field_name, comodel_domain=directory_comodel_domain, **kwargs
            )
        return super(
            File, self.with_context(directory_short_name=True)
        ).search_panel_select_multi_range(field_name, **kwargs)

    # ----------------------------------------------------------
    # Read
    # ----------------------------------------------------------

    @api.depends("name", "directory_id", "directory_id.parent_path")
    def _compute_path(self):
        model = self.env["dms.directory"]
        data = {}
        for record in self:
            path_names = []
            path_json = []
            if record.directory_id.parent_path:
                for directory_id in reversed(
                    list(map(int, record.directory_id.parent_path.split("/")[:-1]))
                ):
                    if not directory_id:
                        break
                    if directory_id not in data:
                        data[directory_id] = model.browse(directory_id)
                    path_names.append(data[directory_id].name)
                    path_json.append(
                        {
                            "model": model._name,
                            "name": data[directory_id].name,
                            "id": directory_id,
                        }
                    )
            path_names.reverse()
            path_json.reverse()
            name = record.name_get()
            path_names.append(name[0][1])
            path_json.append(
                {
                    "model": record._name,
                    "name": name[0][1],
                    "id": isinstance(record.id, int) and record.id or 0,
                }
            )
            record.update(
                {
                    "path_names": "/".join(path_names),
                    "path_json": json.dumps(path_json),
                }
            )

    @api.depends("name")
    def _compute_extension(self):
        for record in self:
            record.extension = file.guess_extension(record.name)

    @api.depends("name", "content")
    def _compute_mimetype(self):
        for record in self:
            binary = base64.b64decode(record.with_context({}).content or "")
            record.res_mimetype = guess_mimetype(
                binary, default="application/octet-stream"
            )

    @api.depends("content_binary", "content_file", "attachment_id")
    def _compute_content(self):
        bin_size = self.env.context.get("bin_size", False)
        for record in self:
            if record.content_file:
                context = {"human_size": True} if bin_size else {"base64": True}
                record.content = record.with_context(context).content_file
            elif record.content_binary:
                record.content = (
                    record.content_binary
                    if bin_size
                    else base64.b64encode(record.content_binary)
                )
            elif record.attachment_id:
                context = {"human_size": True} if bin_size else {"base64": True}
                record.content = record.with_context(context).attachment_id.datas

    @api.depends("content_binary", "content_file")
    def _compute_save_type(self):
        for record in self:
            if record.content_file:
                record.save_type = "file"
            else:
                record.save_type = "database"

    @api.depends("storage_id", "storage_id.save_type")
    def _compute_migration(self):
        storage_model = self.env["dms.storage"]
        save_field = storage_model._fields["save_type"]
        values = save_field._description_selection(self.env)
        selection = {value[0]: value[1] for value in values}
        for record in self:
            storage_type = record.storage_id.save_type
            if storage_type == "attachment" or storage_type == record.save_type:
                record.migration = selection.get(storage_type)
                record.require_migration = False
            else:
                storage_label = selection.get(storage_type)
                file_label = selection.get(record.save_type)
                record.migration = "{} > {}".format(file_label, storage_label)
                record.require_migration = True

    def read(self, fields=None, load="_classic_read"):
        self.check_directory_access("read", {}, True)
        return super(File, self).read(fields, load=load)

    # ----------------------------------------------------------
    # View
    # ----------------------------------------------------------

    @api.onchange("category_id")
    def _change_category(self):
        res = {"domain": {"tag_ids": [("category_id", "=", False)]}}
        if self.category_id:
            res.update(
                {
                    "domain": {
                        "tag_ids": [
                            "|",
                            ("category_id", "=", False),
                            ("category_id", "child_of", self.category_id.id),
                        ]
                    }
                }
            )
        tags = self.tag_ids.filtered(
            lambda rec: not rec.category_id or rec.category_id == self.category_id
        )
        self.tag_ids = tags
        return res

    # ----------------------------------------------------------
    # Security
    # ----------------------------------------------------------

    @api.model
    def _get_directories_from_database(self, file_ids):
        if not file_ids:
            return self.env["dms.directory"]
        return self.env["dms.file"].browse(file_ids).mapped("directory_id")

    @api.model
    def _read_group_process_groupby(self, gb, query):
        if self.env.user.id == SUPERUSER_ID:
            return super(File, self)._read_group_process_groupby(gb, query)
        directories = (
            self.env["dms.directory"].with_context(prefetch_fields=False).search([])
        )
        if directories:
            where_clause = '"{table}"."{field}" = ANY (VALUES {ids})'.format(
                table=self._table,
                field="directory_id",
                ids=", ".join(map(lambda id: "(%s)" % id, directories.ids)),
            )
            query.where_clause += [where_clause]
        else:
            query.where_clause += ["0=1"]
        return super(File, self)._read_group_process_groupby(gb, query)

    @api.model
    def _search(
        self,
        args,
        offset=0,
        limit=None,
        order=None,
        count=False,
        access_rights_uid=None,
    ):
        result = super(File, self)._search(
            args, offset, limit, order, False, access_rights_uid
        )
        if self.env.user.id == SUPERUSER_ID:
            return len(result) if count else result
        # Fix access files with share button (public)
        if self.env.user.has_group("base.group_public"):
            return len(result) if count else result
        # operations
        if not result:
            return 0 if count else []
        file_ids = set(result)
        directories = self._get_directories_from_database(result)
        for directory in directories - directories._filter_access("read"):
            file_ids -= set(directory.sudo().mapped("file_ids").ids)
        return len(file_ids) if count else list(file_ids)

    def _filter_access(self, operation):
        records = super(File, self)._filter_access(operation)
        if self.env.user.id == SUPERUSER_ID:
            return records
        directories = self._get_directories_from_database(records.ids)
        for directory in directories - directories._filter_access("read"):
            records -= self.browse(directory.sudo().mapped("file_ids").ids)
        return records

    def check_access(self, operation, raise_exception=False):
        res = super(File, self).check_access(operation, raise_exception)
        try:
            if self.env.user.has_group("base.group_portal"):
                res_access = res and self.check_directory_access(operation)
                return res_access and (
                    self.directory_id.id
                    not in self.directory_id._get_ids_without_access_groups(operation)
                )
            else:
                return res and self.check_directory_access(operation)
        except AccessError:
            if raise_exception:
                raise
            return False

    def check_directory_access(self, operation, vals=False, raise_exception=False):
        if not vals:
            vals = {}
        if self.env.user.id == SUPERUSER_ID:
            return True
        if "directory_id" in vals and vals["directory_id"]:
            records = self.env["dms.directory"].browse(vals["directory_id"])
        else:
            records = self._get_directories_from_database(self.ids)
        return records.check_access(operation, raise_exception)

    # ----------------------------------------------------------
    # Constrains
    # ----------------------------------------------------------

    @api.constrains("name")
    def _check_name(self):
        for record in self:
            if not file.check_name(record.name):
                raise ValidationError(_("The file name is invalid."))
            files = record.sudo().directory_id.file_ids.name_get()
            if list(
                filter(
                    lambda file: file[1] == record.name and file[0] != record.id, files
                )
            ):
                raise ValidationError(_("A file with the same name already exists."))

    @api.constrains("extension")
    def _check_extension(self):
        for record in self:
            if (
                record.extension
                and record.extension in self._get_forbidden_extensions()
            ):
                raise ValidationError(_("The file has a forbidden file extension."))

    @api.constrains("size")
    def _check_size(self):
        for record in self:
            if record.size and record.size > self._get_binary_max_size() * 1024 * 1024:
                raise ValidationError(
                    _("The maximum upload size is %s MB).")
                    % self._get_binary_max_size()
                )

    @api.constrains("directory_id")
    def _check_directory_access(self):
        for record in self:
            if not record.directory_id.check_access("create", raise_exception=False):
                raise ValidationError(
                    _("The directory has to have the permission to create files.")
                )

    # ----------------------------------------------------------
    # Create, Update, Delete
    # ----------------------------------------------------------

    def _inverse_content(self):
        updates = defaultdict(set)
        for record in self:
            values = self._get_content_inital_vals()
            binary = base64.b64decode(record.content or "")
            values = record._update_content_vals(values, binary)
            updates[tools.frozendict(values)].add(record.id)
        with self.env.norecompute():
            for vals, ids in updates.items():
                self.browse(ids).write(dict(vals))

    def _create_model_attachment(self, vals):
        res_vals = vals.copy()
        directory = self.env["dms.directory"].sudo().browse(res_vals["directory_id"])
        if directory and directory.res_model and directory.res_id:
            attachment = (
                self.env["ir.attachment"]
                .with_context(dms_file=True)
                .create(
                    {
                        "name": vals["name"],
                        "datas": vals["content"],
                        "res_model": directory.res_model,
                        "res_id": directory.res_id,
                    }
                )
            )
            res_vals["attachment_id"] = attachment.id
            res_vals["res_model"] = attachment.res_model
            res_vals["res_id"] = attachment.res_id
            del res_vals["content"]
        return res_vals

    def copy(self, default=None):
        self.ensure_one()
        default = dict(default or [])
        if "directory_id" in default:
            model = self.env["dms.directory"]
            directory = model.browse(default["directory_id"])
            names = directory.sudo().file_ids.mapped("name")
        else:
            names = self.sudo().directory_id.file_ids.mapped("name")
        default.update({"name": file.unique_name(self.name, names, self.extension)})
        self.check_directory_access("create", default, True)
        return super(File, self).copy(default)

    def write(self, vals):
        self.check_directory_access("write", vals, True)
        self.check_lock()
        return super(File, self).write(vals)

    def unlink(self):
        self.check_access_rights("unlink")
        self.check_directory_access("unlink", {}, True)
        self.check_lock()
        # We need to do sudo because we don't know when the related groups
        # will be deleted
        return super(File, self.sudo()).unlink()

    @api.model_create_multi
    def create(self, vals_list):
        new_vals_list = []
        for vals in vals_list:
            if "res_model" not in vals and "res_id" not in vals:
                vals = self._create_model_attachment(vals)
            new_vals_list.append(vals)
        return super(File, self).create(new_vals_list)

    # ----------------------------------------------------------
    # Locking fields and functions
    # ----------------------------------------------------------

    locked_by = fields.Many2one(comodel_name="res.users", string="Locked by")

    is_locked = fields.Boolean(compute="_compute_locked", string="Locked")

    is_lock_editor = fields.Boolean(compute="_compute_locked", string="Editor")

    # ----------------------------------------------------------
    # Locking
    # ----------------------------------------------------------

    def lock(self):
        self.write({"locked_by": self.env.uid})

    def unlock(self):
        self.write({"locked_by": None})

    @api.model
    def _check_lock_editor(self, lock_uid):
        return lock_uid in (self.env.uid, SUPERUSER_ID)

    def check_lock(self):
        for record in self:
            if record.locked_by.exists() and not self._check_lock_editor(
                record.locked_by.id
            ):
                message = _("The record (%s [%s]) is locked, by an other user.")
                raise AccessError(message % (record._description, record.id))

    # ----------------------------------------------------------
    # Read, View
    # ----------------------------------------------------------

    @api.depends("locked_by")
    def _compute_locked(self):
        for record in self:
            if record.locked_by.exists():
                record.update(
                    {
                        "is_locked": True,
                        "is_lock_editor": record.locked_by.id == record.env.uid,
                    }
                )
            else:
                record.update({"is_locked": False, "is_lock_editor": False})
Example #9
0
class student_student(models.Model):
    _name = "student.student"
    _description = "Student Information"
    _inherit = ['mail.thread', 'mail.activity.mixin']

    name = fields.Char(string="Name", required=True, index=True, translate=True)
    active = fields.Boolean(string="Active", default=True)
    image = fields.Binary("Image")
    uni_no = fields.Char(string="Ministry University No.", required=True, copy=False)
    seat_no = fields.Char(string="Seat No.")
    dob = fields.Date(string="Date of Birth", required=True)
    age = fields.Integer(string="Age",readonly=True,store=True,compute="_get_calc_age")
    @api.onchange('dob')
    def _get_calc_age(self):
        for rec in self:
            d1 = datetime.strptime(str(date.today()),"%Y-%m-%d")
            d2 = datetime.strptime(str(rec.dob),"%Y-%m-%d")
            rec.age = (d1-d2).days/365
    gender = fields.Selection([("male", "Male"), ("female", "Female")], "Gender", default="male")
    fdate = fields.Date("First Registration Date")
    ldate = fields.Datetime("Last Registration Date", readonly=True)
    degree_id = fields.Many2one("degree.detail", "Degree to Register For")
    result_ids = fields.One2many("schoolresult.result", "student_id", "Subject's Results")
    hobbies_ids = fields.Many2many("hobbies.details", "student_hobbies_rel", "student_id", "hobbie_id",
                                   "Hobbies Information")
    regfees = fields.Float("Registration Fees", default="0.0")
    tutfees = fields.Float("Tuition Fees ($)", default="0.0")
    @api.onchange('degree_id')
    def _get_tutfees(self):
        for rec in self:
            self.tutfees = self.degree_id.degfees
    totfees = fields.Float(string="Total Fees ($)", store=True, readonly=True, compute='_get_total_fees')

    @api.depends('regfees', 'tutfees')
    def _get_total_fees(self):
        for record in self:
            if not record.regfees:
                self.totfees = record.regfees + record.tutfees
            else:
                self.totfees = record.regfees + record.tutfees

    ref = fields.Reference(
        selection=[("res.partner", "Partner"), ("res.users", "User"), ("student.student", "Student")],
        string="Reference")
    ref_link = fields.Char("External Link")
    responsible_id = fields.Many2one("res.partner", "Responsible Person / Next Of Kin")
    phone = fields.Char(related="responsible_id.phone", string="NOK Phone")
    email = fields.Char(related="responsible_id.email", string="NOK Email")
    health_issues = fields.Selection([("yes", "Yes"), ("no", "No")], "Health Issues", default="no")
    health_notes = fields.Text("Health Issue(s) Details", copy=False)
    template = fields.Html("Template")
    # Represents state of student
    state = fields.Selection(STATE, "Status", readonly=True, default="draft")
    quality_check = fields.Selection([('',''),('good','good'),('very_good','very_good'),('excellent','excellent')],string="Quality Check")
    student_sequence = fields.Char(string='Number', required=True, copy=False, readonly=True,
                                   index=True, default=lambda self: _('New'))

    @api.model
    def create(self, vals):
        if vals.get('student_sequence', _('New')) == _('New'):
            vals['student_sequence'] = self.env['ir.sequence'].next_by_code('student.Sequence') or _('New')
        result = super(student_student, self).create(vals)
        return result
Example #10
0
class PRTMailMessageDraft(models.Model):
    _name = "prt.mail.message.draft"
    _description = "Draft Message"
    _order = 'write_date desc, id desc'
    _rec_name = 'subject'

    # -- Set domain for subtype_id
    def _get_subtypes(self):
        return [('id', 'in', [
            self.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment'),
            self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note')
        ])]

    subject = fields.Char(string="Subject")
    subject_display = fields.Char(string="Subject", compute="_subject_display")
    body = fields.Html(string="Contents",
                       default="",
                       sanitize_style=True,
                       strip_classes=True)

    model = fields.Char(sting="Related Document Model", index=True)
    res_id = fields.Integer(string="Related Document ID", index=True)

    subtype_id = fields.Many2one(string="Message Type",
                                 comodel_name='mail.message.subtype',
                                 domain=_get_subtypes,
                                 default=lambda self: self.env['ir.model.data']
                                 .xmlid_to_res_id('mail.mt_comment'),
                                 required=True)
    parent_id = fields.Integer(string="Parent Message")
    author_id = fields.Many2one(
        string="Author",
        comodel_name='res.partner',
        index=True,
        ondelete='set null',
        default=lambda self: self.env.user.partner_id.id)
    partner_ids = fields.Many2many(string="Recipients",
                                   comodel_name='res.partner')
    record_ref = fields.Reference(string="Message Record",
                                  selection='_referenceable_models',
                                  compute='_record_ref')
    attachment_ids = fields.Many2many(
        string="Attachments",
        comodel_name='ir.attachment',
        relation='prt_message_draft_attachment_rel',
        column1='message_id',
        column2='attachment_id')

    ref_partner_ids = fields.Many2many(string="Followers",
                                       comodel_name='res.partner',
                                       compute='_message_followers')
    ref_partner_count = fields.Integer(string="Followers",
                                       compute='_ref_partner_count')
    wizard_mode = fields.Char(string="Wizard Mode", default='composition')
    signature_location = fields.Selection(
        [('b', 'Before quote'), ('a', 'Message bottom'),
         ('n', 'No signature')],
        string='Signature Location',
        default='b',
        required=True,
        help='Whether to put signature before or after the quoted text.')

    # -- Count ref Partners
    def _ref_partner_count(self):
        for rec in self:
            rec.ref_partner_count = len(rec.ref_partner_ids)

# -- Get related record followers

    @api.depends('record_ref')
    def _message_followers(self):
        for rec in self:
            if rec.record_ref:
                rec.ref_partner_ids = rec.record_ref.message_partner_ids

# -- Get Subject for tree view

    @api.depends('subject')
    def _subject_display(self):

        # Get model names first. Use this method to get translated values
        ir_models = self.env['ir.model'].search([
            ('model', 'in', list(set(self.mapped('model'))))
        ])
        model_dict = {}
        for model in ir_models:
            # Check if model has "name" field
            has_name = self.env['ir.model.fields'].sudo().search_count([
                ('model_id', '=', model.id), ('name', '=', 'name')
            ])
            model_dict.update({model.model: [model.name, has_name]})

        # Compose subject
        for rec in self:
            subject_display = '=== No Reference ==='

            # Has reference
            if rec.record_ref:
                subject_display = model_dict.get(rec.model)[0]

                # Has 'name' field
                if model_dict.get(rec.model, False)[1]:
                    subject_display = "%s: %s" % (subject_display,
                                                  rec.record_ref.name)

                # Has subject
                if rec.subject:
                    subject_display = "%s => %s" % (subject_display,
                                                    rec.subject)

            # Set subject
            rec.subject_display = subject_display

# -- Ref models

    @api.model
    def _referenceable_models(self):
        return [(x.model, x.name)
                for x in self.env['ir.model'].sudo().search([('model', '!=',
                                                              'mail.channel')])
                ]

# -- Compose reference

    @api.depends('res_id')
    def _record_ref(self):
        for rec in self:
            if rec.res_id:
                if rec.model:
                    res = self.env[rec.model].sudo().search([("id", "=",
                                                              rec.res_id)])
                    if res:
                        rec.record_ref = res

# -- Send message

    def send_it(self):
        self.ensure_one()

        # Compose message body
        return _select_draft(self)
Example #11
0
class ConnectorCheckpoint(models.Model):
    _name = "connector.checkpoint"
    _description = "Connector Checkpoint"

    _inherit = ["mail.thread", "mail.activity.mixin"]

    @api.model
    def _reference_models(self):
        models = self.env["ir.model"].search([("state", "!=", "manual")])
        return [(model.model, model.name) for model in models
                if not model.model.startswith("ir.")]

    @api.depends("model_id", "record_id")
    def _compute_record(self):
        for check in self:
            check.record = check.model_id.model + "," + str(check.record_id)

    @api.depends("model_id", "record_id")
    def _compute_name(self):
        for check in self:
            model = self.env[check.model_id.model]
            check.name = model.browse(check.record_id).display_name

    @api.model
    def _search_record(self, operator, value):
        model_model = self.env["ir.model"]
        sql = "SELECT DISTINCT model_id FROM connector_checkpoint"
        self.env.cr.execute(sql)
        model_ids = [row[0] for row in self.env.cr.fetchall()]
        models = model_model.browse(model_ids)

        ids = set()
        for model in models:
            model_id = model.id
            model_name = model.model
            model_obj = self.env[model_name]
            results = model_obj.name_search(name=value, operator=operator)
            res_ids = [res[0] for res in results]
            checks = self.search([("model_id", "=", model_id),
                                  ("record_id", "in", res_ids)])
            ids.update(checks.ids)
        if not ids:
            return [("id", "=", "0")]
        return [("id", "in", tuple(ids))]

    @api.depends("backend_id")
    def _compute_company(self):
        for check in self:
            if hasattr(check.backend_id, "company_id"):
                check.company_id = check.backend_id.company_id.id

    record = fields.Reference(
        compute="_compute_record",
        selection="_reference_models",
        help="The record to review.",
        readonly=True,
    )
    name = fields.Char(
        compute="_compute_name",
        search="_search_record",
        string="Record Name",
        help="Name of the record to review",
        readonly=True,
    )
    record_id = fields.Integer(string="Record ID",
                               required=True,
                               readonly=True)
    model_id = fields.Many2one(comodel_name="ir.model",
                               string="Model",
                               required=True,
                               readonly=True)
    backend_id = fields.Reference(
        string="Imported from",
        selection="_reference_models",
        readonly=True,
        required=True,
        help="The record has been imported from this backend",
        index=True,
    )
    state = fields.Selection(
        selection=[("need_review", "Need Review"), ("reviewed", "Reviewed")],
        string="Status",
        required=True,
        readonly=True,
        default="need_review",
    )
    company_id = fields.Many2one(
        "res.company",
        string="Company",
        readonly=True,
        compute="_compute_company",
        store=True,
    )

    def reviewed(self):
        return self.write({"state": "reviewed"})

    def _subscribe_users(self):
        """ Subscribe all users having the 'Connector Manager' group """
        group = self.env.ref("connector.group_connector_manager")
        if not group:
            return
        users = self.env["res.users"].search([("groups_id", "=", group.id)])
        self.message_subscribe(partner_ids=users.mapped("partner_id").ids)

    @api.model
    def create(self, vals):
        record = super(ConnectorCheckpoint, self).create(vals)
        record._subscribe_users()
        msg = _("A %s needs a review.") % record.model_id.name
        record.message_post(body=msg, subtype="mail.mt_comment")
        return record

    @api.model
    def create_from_name(self, model_name, record_id, backend_model_name,
                         backend_id):
        model_model = self.env["ir.model"]
        model = model_model.search([("model", "=", model_name)], limit=1)
        assert model, "The model %s does not exist" % model_name
        backend = backend_model_name + "," + str(backend_id)
        return self.create({
            "model_id": model.id,
            "record_id": record_id,
            "backend_id": backend
        })

    @api.model
    def _needaction_domain_get(self):
        """ Returns the domain to filter records that require an action
            :return: domain or False is no action
        """
        return [("state", "=", "need_review")]
Example #12
0
class StockInventoryLine(models.Model):
    _inherit = 'stock.inventory.line'

    calc_product_cost = fields.Float(compute='_compute_product_cost',
                                     string='Computed cost',
                                     store=True,
                                     digits=dp.get_precision('Account'))
    manual_product_cost = fields.Float(string='Manual cost',
                                       digits=dp.get_precision('Account'))
    value = fields.Float(compute='_compute_value',
                         string='Value',
                         store=True,
                         digits=dp.get_precision('Account'))
    cost_origin = fields.Text(
        compute='_compute_product_cost',
        store=True,
        help='Explain computation method for each product')
    reference = fields.Reference(selection='_select_models',
                                 compute='_compute_product_cost',
                                 store=True)

    def _select_models(self):
        models = self.env['ir.model'].search([('model', 'in',
                                               self._get_models())])
        return [(x['model'], x['name']) for x in models] + [('', '')]

    @api.multi
    @api.depends('product_id', 'product_qty', 'manual_product_cost',
                 'inventory_id.state', 'inventory_id.to_recompute')
    def _compute_product_cost(self):
        custom_data_source = self._get_custom_data_source()
        po_l_obj = self.env['purchase.order.line']
        product_ids = [x.product_id.id for x in self]
        # product_ids == [False] for first created line
        if not product_ids or not product_ids[0]:
            return
        invoices = self._get_invoice_data(product_ids,
                                          company=self.env.user.company_id)
        for line in self:
            # DON'T FORGET TO update SELECTION_ORDER
            # when changes are done in the order of the blocks
            if line.inventory_id.state not in ('done') and \
                    not line.inventory_id.to_recompute:
                line.cost_origin = _("Click on 'Compute cost'")
                continue
            if not line.inventory_id or not line.product_id:
                continue
            # get cost from supplier invoice
            explanation = ''
            cost_price = 0
            reference = False
            if not cost_price:
                # get cost price from supplier info
                sup_info = line.product_id.seller_ids
                if sup_info and sup_info[0].price:
                    cost_price = sup_info[0].price
                    explanation = _('Supplier info')
                    reference = 'product.supplierinfo,%s' % sup_info[0].id
            if not cost_price and custom_data_source:
                cost_price, explanation, reference = self._get_custom_cost(
                    custom_data_source, product_ids)
            if not cost_price and invoices:
                cost_price = invoices[line.product_id.id].get('price_unit')
                explanation = _('Supplier Invoice')
                if invoices[line.product_id.id].get('id'):
                    reference = 'account.invoice,%s' % invoices[
                        line.product_id.id].get('id')
            if not cost_price:
                # get cost price from purchase
                po_line = po_l_obj.search(
                    [('product_id', '=', line.product_id.id),
                     ('order_id.state', '=', 'done')],
                    limit=1)
                if po_line:
                    cost_price = po_line.price_unit
                    explanation = _('Purchase')
                    reference = 'purchase.order,%s' % po_line.order_id.id
            if not cost_price:
                if line.product_id.standard_price_:
                    cost_price = line.product_id.standard_price_
                    explanation = _('Product manual standard_price_')
                    reference = 'product.product,%s' % line.product_id.id
            if not cost_price:
                if line.product_id.standard_price:
                    cost_price = line.product_id.standard_price
                    explanation = _('Product standard_price')
                    reference = 'product.product,%s' % line.product_id.id
            if not cost_price:
                explanation = _('No Cost found')
                reference = ''
            line.calc_product_cost = cost_price
            line.cost_origin = explanation
            if reference:
                line.reference = reference

    def _get_custom_data_source(self):
        return None

    def _get_custom_cost(self, custom_data_source, product_ids):
        return (None, None, None)

    @api.multi
    @api.depends('calc_product_cost', 'manual_product_cost', 'product_qty')
    def _compute_value(self):
        for line in self:
            if line.manual_product_cost:
                line.value = line.manual_product_cost * line.product_qty
            else:
                line.value = line.calc_product_cost * line.product_qty

    @api.model
    def _get_invoice_data(self, product_ids, company):
        invoices = defaultdict(dict)
        query = """ SELECT l.product_id, max(l.create_date) AS date
            FROM account_invoice_line l
                LEFT JOIN account_invoice i ON i.id = l.invoice_id
            WHERE l.product_id IN %s AND i.company_id = %s
              AND i.type = 'in_invoice' AND i.state in ('open', 'paid')
            GROUP BY 1
            ORDER BY date ASC
            LIMIT 1
        """
        self.env.cr.execute(query, (tuple(product_ids), company.id))
        oldier = self.env.cr.fetchall()
        if oldier:
            query = """ SELECT l.product_id, l.price_unit, i.id, i.number
                FROM account_invoice_line l
                    LEFT JOIN account_invoice i ON i.id = l.invoice_id
                WHERE l.product_id IN %s AND i.company_id = %s
                    AND l.create_date >= %s
                    AND i.type = 'in_invoice' AND i.state in ('open', 'paid')
                ORDER BY l.create_date ASC
            """
            self.env.cr.execute(query,
                                (tuple(product_ids), company.id, oldier[0][1]))
            res = self.env.cr.fetchall()
            invoices = defaultdict(dict)
            for elm in res:
                invoices[elm[0]].update({
                    'price_unit': elm[1],
                    'id': elm[2],
                    'number': elm[3]
                })
        return invoices

    @api.model
    def _get_models(self):
        return [
            'account.invoice',
            'purchase.order',
            'product.supplierinfo',
            'product.product',
        ]

    @api.multi
    def button_info_origin(self):
        self.ensure_one()
        if self.reference:
            return {
                'type':
                'ir.actions.act_window',
                'view_mode':
                'form',
                'res_model':
                self.reference._model._name,
                'res_id':
                self.reference.id,
                'target':
                'new',
                'name':
                _("%s: %s: '%s'" % (self.reference._model._description,
                                    self.product_id.display_name, self.value)),
            }
        return False
Example #13
0
class Voucher(models.Model):
    '''新建凭证'''
    _name = 'voucher'
    _inherit = ['mail.thread']
    _order = 'period_id, name desc'
    _description = u'会计凭证'

    @api.model
    def _default_voucher_date(self):
        return self._default_voucher_date_impl()

    @api.model
    def _default_voucher_date_impl(self):
        ''' 获得默认的凭证创建日期 '''
        voucher_setting = self.env['ir.values'].get_default(
            'finance.config.settings', 'default_voucher_date')
        now_date = fields.Date.context_today(self)
        if voucher_setting == 'last' and self.search([], limit=1):
            create_date = self.search([], limit=1).date
        else:
            create_date = now_date
        return create_date

    @api.model
    def _select_objects(self):
        records = self.env['business.data.table'].search([])
        models = self.env['ir.model'].search([
            ('model', 'in', [record.name for record in records])
        ])
        return [(model.model, model.name) for model in models]

    @api.one
    @api.depends('date')
    def _compute_period_id(self):
        self.period_id = self.env['finance.period'].get_period(self.date)

    document_word_id = fields.Many2one(
        'document.word',
        u'凭证字',
        ondelete='restrict',
        required=True,
        default=lambda self: self.env.ref('finance.document_word_1'))
    date = fields.Date(u'凭证日期',
                       required=True,
                       default=_default_voucher_date,
                       states=READONLY_STATES,
                       track_visibility='always',
                       help=u'本张凭证创建的时间',
                       copy=False)
    name = fields.Char(u'凭证号', track_visibility='always', copy=False)
    att_count = fields.Integer(u'附单据',
                               default=1,
                               help=u'原始凭证的张数',
                               states=READONLY_STATES)
    period_id = fields.Many2one('finance.period',
                                u'会计期间',
                                compute='_compute_period_id',
                                ondelete='restrict',
                                store=True,
                                help=u'本张凭证发生日期对应的,会计期间')
    line_ids = fields.One2many(
        'voucher.line',
        'voucher_id',
        u'凭证明细',
        copy=True,
        states=READONLY_STATES,
    )
    amount_text = fields.Float(u'总计',
                               compute='_compute_amount',
                               store=True,
                               track_visibility='always',
                               help=u'凭证金额')
    state = fields.Selection([('draft', u'草稿'), ('done', u'已确认'),
                              ('cancel', u'已作废')],
                             u'状态',
                             default='draft',
                             index=True,
                             track_visibility='always',
                             help=u'凭证所属状态!')
    is_checkout = fields.Boolean(u'结账凭证', help=u'是否是结账凭证')
    is_init = fields.Boolean(u'是否初始化凭证', help=u'是否是初始化凭证')
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())
    ref = fields.Reference(string=u'前置单据', selection='_select_objects')

    @api.one
    def voucher_done(self):
        """
        确认 凭证按钮 所调用的方法
        :return: 主要是把 凭证的 state改变
        """
        if self.state == 'done':
            raise UserError(u'凭证%s已经确认,请不要重复确认!' % self.name)
        if self.period_id.is_closed:
            raise UserError(u'该会计期间已结账!不能确认')
        if not self.line_ids:
            raise ValidationError(u'请输入凭证行')
        for line in self.line_ids:
            if line.debit + line.credit == 0:
                raise ValidationError(u'单行凭证行 %s 借和贷不能同时为0' %
                                      line.account_id.name)
            if line.debit * line.credit != 0:
                raise ValidationError(
                    u'单行凭证行不能同时输入借和贷\n 摘要为%s的凭证行 借方为:%s 贷方为:%s' %
                    (line.name, line.debit, line.credit))
        debit_sum = sum([line.debit for line in self.line_ids])
        credit_sum = sum([line.credit for line in self.line_ids])
        precision = self.env['decimal.precision'].precision_get('Amount')
        debit_sum = round(debit_sum, precision)
        credit_sum = round(credit_sum, precision)
        if debit_sum != credit_sum:
            raise ValidationError(u'借贷方不平,无法确认!\n 借方合计:%s 贷方合计:%s' %
                                  (debit_sum, credit_sum))

        self.state = 'done'
        ''' #xuan
        if self.is_checkout:   # 月结凭证不做反转
            return True
        for line in self.line_ids:
            if line.account_id.costs_types == 'out' and line.credit:
                # 费用类科目只能在借方记账,比如银行利息收入
                line.debit = -line.credit
                line.credit = 0
            if line.account_id.costs_types == 'in' and line.debit:
                # 收入类科目只能在贷方记账,比如退款给客户的情况
                line.credit = -line.debit
                line.debit = 0
        '''

    @api.one
    def voucher_can_be_draft(self):
        if self.ref:
            raise UserError(u'不能撤销确认由其他单据生成的凭证!')
        self.voucher_draft()

    @api.one
    def voucher_draft(self):
        if self.state == 'draft':
            raise UserError(u'凭证%s已经撤销确认,请不要重复撤销!' % self.name)
        if self.period_id.is_closed:
            raise UserError(u'%s期 会计期间已结账!不能撤销确认' % self.period_id.name)

        self.state = 'draft'

    @api.one
    @api.depends('line_ids')
    def _compute_amount(self):
        # todo 实现明细行总金额
        self.amount_text = str(sum([line.debit for line in self.line_ids]))

    # 重载write 方法
    @api.multi
    def write(self, vals):
        for order in self:  # 还需要进一步优化
            if self.env.context.get('call_module', False) == "checkout_wizard":
                return super(Voucher, self).write(vals)
            if order.period_id.is_closed is True:
                raise UserError(u'%s期 会计期间已结账,凭证不能再修改!' % order.period_id.name)
            if len(vals) == 1 and vals.get('state', False):  # 确认or撤销确认
                return super(Voucher, self).write(vals)
            else:
                order = self.browse(order.id)
                if order.state == 'done':
                    raise UserError(u'凭证%s已确认!修改请先撤销!' % order.name)
            return super(Voucher, self).write(vals)
Example #14
0
class File(models.Model):

    _name = 'ag_dms.file'
    _description = "File"
    _inherit = [
        'ag_dms.locking', 'ag_dms.access', 'mail.thread', 'mail.activity.mixin'
    ]

    #----------------------------------------------------------
    # Database
    #----------------------------------------------------------

    name = fields.Char(string="Filename", required=True, index=True)

    file_name = fields.Char(string="ContentFilename", index=True)

    active = fields.Boolean(
        string="Archived",
        default=True,
        help=
        "If a file is set to archived, it is not displayed, but still exists.")

    is_top_file = fields.Boolean(string="Top Directory",
                                 compute='_compute_is_top_file',
                                 search='_search_is_top_file')

    settings = fields.Many2one(comodel_name='ag_dms.settings',
                               string="Settings",
                               store=True,
                               auto_join=True,
                               ondelete='restrict',
                               compute='_compute_settings')

    show_tree = fields.Boolean(string="Show Structure",
                               related="settings.show_tree",
                               readonly=True)

    color = fields.Integer(string="Color")

    tags = fields.Many2many(comodel_name='ag_dms.tag',
                            relation='ag_dms_file_tag_rel',
                            column1='fid',
                            column2='tid',
                            string='Tags')

    category = fields.Many2one(string="Category",
                               comodel_name='ag_dms.category',
                               related="directory.category",
                               readonly=True,
                               store=True)

    content = fields.Binary(string='Content',
                            required=True,
                            compute='_compute_content',
                            inverse='_inverse_content')

    reference = fields.Reference(selection=[('ag_dms.data', _('Data'))],
                                 string="Data Reference",
                                 readonly=True)

    directory = fields.Many2one(comodel_name='ag_dms.directory',
                                string="Directory",
                                domain="[('permission_create', '=', True)]",
                                ondelete='restrict',
                                auto_join=True,
                                required=True)

    extension = fields.Char(string='Extension',
                            compute='_compute_extension',
                            readonly=True,
                            store=True)

    mimetype = fields.Char(string='Type',
                           compute='_compute_mimetype',
                           readonly=True,
                           store=True)

    size = fields.Integer(string='Size', readonly=True)

    path = fields.Char(string="Path",
                       store=True,
                       index=True,
                       readonly=True,
                       compute='_compute_path')

    relational_path = fields.Text(string="Path",
                                  store=True,
                                  readonly=True,
                                  compute='_compute_relational_path')

    custom_thumbnail = fields.Binary(string="Custom Thumbnail")

    custom_thumbnail_medium = fields.Binary(string="Medium Custom Thumbnail")

    custom_thumbnail_small = fields.Binary(string="Small Custom Thumbnail")

    thumbnail = fields.Binary(compute='_compute_thumbnail', string="Thumbnail")

    thumbnail_medium = fields.Binary(compute='_compute_thumbnail_medium',
                                     string="Medium Thumbnail")

    thumbnail_small = fields.Binary(compute='_compute_thumbnail_small',
                                    string="Small Thumbnail")

    #----------------------------------------------------------
    # Functions
    #----------------------------------------------------------

    @api.multi
    def notify_change(self, values, *largs, **kwargs):
        super(File, self).notify_change(values, *largs, **kwargs)
        if "save_type" in values:
            self._update_reference_type()

    @api.multi
    def trigger_computation_up(self, fields, *largs, **kwargs):
        if ("no_trigger_computation_up" not in self.env.context):
            self.mapped('directory').suspend_security().with_context(
                is_parent=True).trigger_computation(fields)

    @api.multi
    def trigger_computation(self, fields, *largs, **kwargs):
        super(File, self).trigger_computation(fields,
                                              refresh=True,
                                              *largs,
                                              **kwargs)
        for record in self:
            values = {}
            if "settings" in fields:
                values.update(record._compute_settings(write=False))
            if "path" in fields:
                values.update(record._compute_path(write=False))
                values.update(record._compute_relational_path(write=False))
            if "extension" in fields:
                values.update(record._compute_extension(write=False))
            if "mimetype" in fields:
                values.update(record._compute_mimetype(write=False))
            if values:
                record.write(values)
                if "settings" in fields:
                    record.notify_change(
                        {'save_type': record.settings.save_type})

    @api.model
    def max_upload_size(self):
        params = self.env['ir.config_parameter'].sudo()
        return int(params.get_param('ag_dms.max_upload_size', default=25))

    @api.model
    def forbidden_extensions(self):
        params = self.env['ir.config_parameter'].sudo()
        forbidden_extensions = params.get_param('ag_dms.forbidden_extensions',
                                                default="")
        return [x.strip() for x in forbidden_extensions.split(',')]

    #----------------------------------------------------------
    # Search
    #----------------------------------------------------------

    @api.model
    def _search_permission_create(self, operator, operand):
        res = super(File, self)._search_permission_create(operator, operand)
        records = self.browse(
            res[0][2]).filtered(lambda r: r.directory.check_access('create'))
        if operator == '=' and operand:
            return [('id', 'in', records.mapped('id'))]
        return [('id', 'not in', records.mapped('id'))]

    @api.model
    def _search_is_top_file(self, operator, operand):
        files = self.search(
            []).filtered(lambda f: not f.directory.check_access('read'))
        if operator == '=' and operand:
            return [('id', 'in', files.mapped('id'))]
        return [('id', 'not in', files.mapped('id'))]

    #----------------------------------------------------------
    # Read, View
    #----------------------------------------------------------

    @api.multi
    def _compute_settings(self, write=True):
        if write:
            for record in self:
                record.settings = record.directory.settings
        else:
            self.ensure_one()
            return {'settings': self.directory.settings.id}

    @api.multi
    def _compute_extension(self, write=True):
        if write:
            for record in self:
                record.extension = os.path.splitext(record.name)[1]
        else:
            self.ensure_one()
            return {'extension': os.path.splitext(self.name)[1]}

    @api.multi
    def _compute_mimetype(self, write=True):
        def get_mimetype(record):
            mimetype = mimetypes.guess_type(record.name)[0]
            if (not mimetype or mimetype
                    == 'application/octet-stream') and record.content:
                mimetype = guess_mimetype(base64.b64decode(record.content))
            return mimetype or 'application/octet-stream'

        if write:
            for record in self:
                record.mimetype = get_mimetype(record)
        else:
            self.ensure_one()
            return {'mimetype': get_mimetype(self)}

    @api.multi
    def _compute_path(self, write=True):
        if write:
            for record in self:
                record.path = "%s%s" % (record.directory.path, record.name)
        else:
            self.ensure_one()
            return {'path': "%s%s" % (self.directory.path, self.name)}

    @api.multi
    def _compute_relational_path(self, write=True):
        def get_relational_path(record):
            path = json.loads(record.directory.relational_path)
            path.append({
                'model': record._name,
                'id': record.id,
                'name': record.name
            })
            return json.dumps(path)

        if write:
            for record in self:
                record.relational_path = get_relational_path(record)
        else:
            self.ensure_one()
            return {'relational_path': get_relational_path(self)}

    @api.multi
    def _compute_content(self):
        for record in self:
            record.content = record._get_content()

    @api.multi
    def _compute_permissions(self):
        super(File, self)._compute_permissions()
        for record in self:
            record.permission_create = record.permission_create and record.directory.check_access(
                'create')

    @api.depends('directory')
    def _compute_is_top_file(self):
        for record in self:
            record.is_top_file = record.directory.check_access('read')

    @api.depends('custom_thumbnail')
    def _compute_thumbnail(self):
        for record in self:
            if record.custom_thumbnail:
                record.thumbnail = record.with_context({}).custom_thumbnail
            else:
                extension = record.extension and record.extension.strip(
                    ".") or ""
                path = os.path.join(_img_path, "file_%s.png" % extension)
                if not os.path.isfile(path):
                    path = os.path.join(_img_path, "file_unkown.png")
                with open(path, "rb") as image_file:
                    record.thumbnail = base64.b64encode(image_file.read())

    @api.depends('custom_thumbnail_medium')
    def _compute_thumbnail_medium(self):
        for record in self:
            if record.custom_thumbnail_medium:
                record.thumbnail_medium = record.with_context(
                    {}).custom_thumbnail_medium
            else:
                extension = record.extension and record.extension.strip(
                    ".") or ""
                path = os.path.join(_img_path,
                                    "file_%s_128x128.png" % extension)
                if not os.path.isfile(path):
                    path = os.path.join(_img_path, "file_unkown_128x128.png")
                with open(path, "rb") as image_file:
                    record.thumbnail_medium = base64.b64encode(
                        image_file.read())

    @api.depends('custom_thumbnail_small')
    def _compute_thumbnail_small(self):
        for record in self:
            if record.custom_thumbnail_small:
                record.thumbnail_small = record.with_context(
                    {}).custom_thumbnail_small
            else:
                extension = record.extension and record.extension.strip(
                    ".") or ""
                path = os.path.join(_img_path, "file_%s_64x64.png" % extension)
                if not os.path.isfile(path):
                    path = os.path.join(_img_path, "file_unkown_64x64.png")
                with open(path, "rb") as image_file:
                    record.thumbnail_small = base64.b64encode(
                        image_file.read())

    #----------------------------------------------------------
    # Create, Update, Delete
    #----------------------------------------------------------

    @api.constrains('name')
    def _check_name(self):
        if not self.check_name(self.name):
            raise ValidationError(_("The file name is invalid."))
        childs = self.sudo().directory.files.mapped(
            lambda rec: [rec.id, rec.name])
        duplicates = [
            rec for rec in childs if rec[1] == self.name and rec[0] != self.id
        ]
        if duplicates:
            raise ValidationError(
                _("A file with the same name already exists."))

    @api.constrains('name')
    def _check_extension(self):
        forbidden_extensions = self.forbidden_extensions()
        file_extension = self._compute_extension(write=False)['extension']
        if file_extension and file_extension in forbidden_extensions:
            raise ValidationError(
                _("The file has a forbidden file extension."))

    @api.constrains('content')
    def _check_size(self):
        max_upload_size = self.max_upload_size()
        if (max_upload_size * 1024 * 1024) < len(base64.b64decode(
                self.content)):
            raise ValidationError(
                _("The maximum upload size is %s MB).") % max_upload_size)

    @api.constrains('directory')
    def _check_directory_access(self):
        for record in self:
            record.directory.check_access('create', raise_exception=True)

    @api.multi
    def _inverse_content(self):
        for record in self:
            if record.content:
                content = record.content
                size = len(base64.b64decode(content))
                reference = record.reference
                if reference:
                    record._update_reference_values({'content': content})
                    record.write({'size': size})
                else:
                    directory = record.directory
                    settings = record.settings if record.settings else directory.settings
                    save_type = settings and settings.read(['save_type'])
                    reference = record._create_reference(
                        settings, directory.path, record.name, content)
                    if reference:
                        reference = "%s,%s" % (reference._name, reference.id)
                        record.write({'reference': reference, 'size': size})
                    else:
                        _logger.warning(
                            "Something went wrong %s not supported!" %
                            save_type)
            else:
                record._unlink_reference()
                record.reference = None

    @api.model
    def _before_create(self, vals, *largs, **kwargs):
        tools.image_resize_images(vals,
                                  big_name='custom_thumbnail',
                                  medium_name='custom_thumbnail_medium',
                                  small_name='custom_thumbnail_small')
        return super(File, self)._before_create(vals, *largs, **kwargs)

    @api.multi
    def _before_write(self, vals, *largs, **kwargs):
        tools.image_resize_images(vals,
                                  big_name='custom_thumbnail',
                                  medium_name='custom_thumbnail_medium',
                                  small_name='custom_thumbnail_small')
        return super(File, self)._before_write(vals, *largs, **kwargs)

    @api.multi
    def _before_unlink(self, *largs, **kwargs):
        info = super(File, self)._before_unlink(*largs, **kwargs)
        records = self.filtered(lambda r: r.reference)
        references = [
            list((k, list(map(lambda rec: rec.reference.id, v))))
            for k, v in itertools.groupby(
                records.sorted(key=lambda rec: rec.reference._name),
                lambda rec: rec.reference._name)
        ]
        info['references'] = references
        return info

    @api.multi
    def _after_unlink(self, result, info, infos, *largs, **kwargs):
        super(File, self)._after_unlink(result, info, infos, *largs, **kwargs)
        if 'references' in info and info['references']:
            for tuple in info['references']:
                self.env[tuple[0]].sudo().browse(list(filter(
                    None, tuple[1]))).unlink()

    @api.returns('self', lambda value: value.id)
    def copy(self, default=None):
        self.ensure_one()
        default = dict(default or [])
        names = []
        if 'directory' in default:
            directory = self.env['ag_dms.directory'].sudo().browse(
                default['directory'])
            names = directory.files.mapped('name')
        else:
            names = self.sudo().directory.files.mapped('name')
        default.update(
            {'name': self.unique_name(self.name, names, self.extension)})
        vals = self.copy_data(default)[0]
        if 'reference' in vals:
            del vals['reference']
        if not 'content' in vals:
            vals.update({'content': self.content})
        new = self.with_context(lang=None).create(vals)
        self.copy_translations(new)
        return new

    @api.multi
    def _check_recomputation(self, vals, olds, *largs, **kwargs):
        super(File, self)._check_recomputation(vals, olds, *largs, **kwargs)
        fields = []
        if 'name' in vals:
            fields.extend(['extension', 'mimetype', 'path'])
        if 'directory' in vals:
            fields.extend(['settings', 'path'])
        if 'content' in vals:
            fields.extend(['index_content'])
        if fields:
            self.trigger_computation(fields)
        self._check_reference_values(vals)

    #----------------------------------------------------------
    # Reference
    #----------------------------------------------------------

    @api.multi
    def _create_reference(self, settings, path, filename, content):
        self.ensure_one()
        self.check_access('create', raise_exception=True)
        if settings.read(['save_type']) and settings.save_type == 'database':
            return self.env['ag_dms.data_database'].sudo().create(
                {'data': content})
        return None

    @api.multi
    def _update_reference_values(self, values):
        self.check_access('write', raise_exception=True)
        references = self.sudo().mapped('reference')
        if references:
            references.sudo().update(values)

    @api.multi
    def _update_reference_type(self):
        self.check_access('write', raise_exception=True)
        for record in self:
            save_type = record.settings.read([
                'save_type'
            ]) and record.settings and record.settings.save_type
            if record.reference and save_type and save_type != record.reference.type(
            ):
                reference = record._create_reference(record.settings,
                                                     record.directory.path,
                                                     record.name,
                                                     record.content)
                if reference:
                    record._unlink_reference()
                    record.reference = "%s,%s" % (reference._name,
                                                  reference.id)
                else:
                    _logger.warning("Something went wrong %s not supported!" %
                                    save_type)

    @api.multi
    def _check_reference_values(self, values):
        self.check_access('write', raise_exception=True)
        if 'content' in values:
            self._update_reference_values({'content': values['content']})
        if 'settings' in values:
            self._update_reference_type()

    @api.multi
    def _get_content(self):
        self.ensure_one()
        self.check_access('read', raise_exception=True)
        return self.reference.sudo().content() if self.reference else None

    @api.multi
    def _unlink_reference(self):
        self.check_access('unlink', raise_exception=True)
        record = self.filtered(lambda r: r.reference)
        for tuple in [
                list((k, list(map(lambda rec: rec.reference.id, v))))
                for k, v in itertools.groupby(
                    record.sorted(key=lambda rec: rec.reference._name),
                    lambda rec: rec.reference._name)
        ]:
            self.env[tuple[0]].sudo().browse(list(filter(None,
                                                         tuple[1]))).unlink()
Example #15
0
class ExternalSystem(models.Model):

    _name = 'external.system'
    _description = 'External System'

    name = fields.Char(
        required=True,
        help='This is the canonical (humanized) name for the system.',
    )
    host = fields.Char(
        help='This is the domain or IP address that the system can be reached '
        'at.', )
    port = fields.Integer(
        help='This is the port number that the system is listening on.', )
    username = fields.Char(
        help='This is the username that is used for authenticating to this '
        'system, if applicable.', )
    password = fields.Char(
        help='This is the password that is used for authenticating to this '
        'system, if applicable.', )
    private_key = fields.Text(
        help='This is the private key that is used for authenticating to '
        'this system, if applicable.', )
    private_key_password = fields.Text(
        help='This is the password to unlock the private key that was '
        'provided for this sytem.', )
    fingerprint = fields.Text(
        help='This is the fingerprint that is advertised by this system in '
        'order to validate its identity.', )
    ignore_fingerprint = fields.Boolean(
        default=True,
        help='Set this to `True` in order to ignore an invalid/unknown '
        'fingerprint from the system.',
    )
    remote_path = fields.Char(
        help='Restrict to this directory path on the remote, if applicable.', )
    company_ids = fields.Many2many(
        string='Companies',
        comodel_name='res.company',
        required=True,
        default=lambda s: [(6, 0, s.env.user.company_id.ids)],
        help='Access to this system is restricted to these companies.',
    )
    system_type = fields.Selection(
        selection='_get_system_types',
        required=True,
    )
    interface = fields.Reference(
        selection='_get_system_types',
        readonly=True,
        help='This is the interface that this system represents. It is '
        'created automatically upon creation of the external system.',
    )

    _sql_constraints = [
        ('name_uniq', 'UNIQUE(name)', 'Connection name must be unique.'),
    ]

    @api.model
    def _get_system_types(self):
        """Return the adapter interface models that are installed."""
        adapter = self.env['external.system.adapter']
        return [(m, self.env[m]._description)
                for m in adapter._inherit_children]

    @api.multi
    @api.constrains('fingerprint', 'ignore_fingerprint')
    def check_fingerprint_ignore_fingerprint(self):
        """Do not allow a blank fingerprint if not set to ignore."""
        for record in self:
            if not record.ignore_fingerprint and not record.fingerprint:
                raise ValidationError(
                    _(
                        'Fingerprint cannot be empty when Ignore Fingerprint is '
                        'not checked.', ))

    @api.multi
    @contextmanager
    def client(self):
        """Client object usable as a context manager to include destruction.

        Yields the result from ``external_get_client``, then calls
        ``external_destroy_client`` to cleanup the client.

        Yields:
            mixed: An object representing the client connection to the remote
             system.
        """
        with self.interface.client() as client:
            yield client

    @api.model
    def create(self, vals):
        """Create the interface for the record and assign to ``interface``."""
        record = super(ExternalSystem, self).create(vals)
        if not self.env.context.get('no_create_interface'):
            interface = self.env[vals['system_type']].create({
                'system_id':
                record.id,
            })
            record.interface = interface
        return record

    @api.multi
    def action_test_connection(self):
        """Test the connection to the external system."""
        self.ensure_one()
        self.interface.external_test_connection()
Example #16
0
class SDP(models.Model):
    _name = 'wbs.cost.line'
    _rec_name = 'wbs_id'

    wbs_id = fields.Many2one('project.wbs', u"WBS", required=True)
    type_id = fields.Many2one('wbs.cost.type', u"Type", required=True)
    reference = fields.Reference(string='Document',
                                 selection='_reference_models',
                                 required=True)
    unit_cost = fields.Float(u'Coût unitaire')
    account_id = fields.Many2one('account.account', string=u'Compte comptable')
    product_id = fields.Many2one('product.product', string=u'Product')
    qty = fields.Float(u'Quatité', required=True)
    date = fields.Date(u'Date', required=True)
    cost = fields.Float(u'Coût total', compute="get_price")
    analytic_line_id = fields.Many2one('account.analytic.line',
                                       u'Analytical line',
                                       readonly=True)

    def name_get(self):
        res = []
        for line in self:
            name = line.type_id.name
            if line.reference:
                name += '/' + str(line.reference.name)
            res.append((line.id, name))
        return res

    def get_price(self):
        for record in self:
            record.cost = record.qty * record.unit_cost

    @api.model
    def _reference_models(self):
        types = self.env['wbs.cost.type'].search([])
        models = []
        for type in types:
            models.append((type.model_id.model, type.model_id.name))
        return models

    @api.onchange('reference')
    def onchange_reference(self):
        if self.reference:
            type = self.env['wbs.cost.type'].search([('model_id.model', '=',
                                                      self.reference._name)])
            if type:
                account_id = False
                self.type_id = type.id
                if self.reference.read([type.field_id.name]):
                    self.unit_cost = self.reference.read(
                        [type.field_id.name])[0][type.field_id.name]
                print('ffffff', type.account_field_id.name)
                if type.account_field_id.name and self.reference.read(
                    [type.account_field_id.name]):
                    if self.reference.read([
                            type.account_field_id.name
                    ])[0].get(type.account_field_id.name, False):
                        account_id = self.reference.read([
                            type.account_field_id.name
                        ])[0][type.account_field_id.name]
                if not account_id:
                    if type.account_id:
                        account_id = type.account_id
                self.account_id = account_id
                if type.product_field_id.name and self.reference.read([
                        type.product_field_id.name
                ])[0].get(type.product_field_id.name, False):
                    self.product_id = self.reference.read([
                        type.product_field_id.name
                    ])[0][type.product_field_id.name]

    @api.onchange('type_id')
    def onchange_type(self):
        if self.reference:
            type = self.env['wbs.cost.type'].search([('model_id.model', '=',
                                                      self.reference._name)])
            if type:
                self.type_id = type.id
Example #17
0
class Subscription(models.Model):
    _name = "subscription.subscription"
    _description = "Subscription"

    name = fields.Char(required=True)
    active = fields.Boolean(
        help=
        "If the active field is set to False, it will allow you to hide the subscription without removing it.",
        default=True)
    partner_id = fields.Many2one('res.partner', string='Partner')
    notes = fields.Text(string='Internal Notes')
    user_id = fields.Many2one('res.users',
                              string='User',
                              required=True,
                              default=lambda self: self.env.user)
    interval_number = fields.Integer(string='Internal Qty', default=1)
    interval_type = fields.Selection([('days', 'Days'), ('weeks', 'Weeks'),
                                      ('months', 'Months')],
                                     string='Interval Unit',
                                     default='months')
    exec_init = fields.Integer(string='Number of Documents')
    date_init = fields.Datetime(string='First Date',
                                default=fields.Datetime.now)
    state = fields.Selection([('draft', 'Draft'), ('running', 'Running'),
                              ('done', 'Done')],
                             string='Status',
                             copy=False,
                             default='draft')
    doc_source = fields.Reference(
        selection=_get_document_types,
        string='Source Document',
        required=True,
        help=
        "User can choose the source document on which he wants to create documents"
    )
    doc_lines = fields.One2many('subscription.subscription.history',
                                'subscription_id',
                                string='Documents created',
                                readonly=True)
    cron_id = fields.Many2one('ir.cron',
                              string='Cron Job',
                              help="Scheduler which runs on subscription",
                              states={
                                  'running': [('readonly', True)],
                                  'done': [('readonly', True)]
                              })
    note = fields.Text(string='Notes',
                       help="Description or Summary of Subscription")

    @api.model
    def _auto_end(self):
        super(Subscription, self)._auto_end()
        # drop the FK from subscription to ir.cron, as it would cause deadlocks
        # during cron job execution. When model_copy() tries to write() on the subscription,
        # it has to wait for an ExclusiveLock on the cron job record, but the latter
        # is locked by the cron system for the duration of the job!
        # FIXME: the subscription module should be reviewed to simplify the scheduling process
        #        and to use a unique cron job for all subscriptions, so that it never needs to
        #        be updated during its execution.
        self.env.cr.execute("ALTER TABLE %s DROP CONSTRAINT %s" %
                            (self._table, '%s_cron_id_fkey' % self._table))

    @api.multi
    def set_process(self):
        model_subscription = self.env['ir.model'].search([('model', '=',
                                                           self._name)])
        for subscription in self:
            cron_data = {
                'name': subscription.name,
                'interval_number': subscription.interval_number,
                'interval_type': subscription.interval_type,
                'numbercall': subscription.exec_init,
                'nextcall': subscription.date_init,
                'model_id': model_subscription.id,
                'state': 'code',
                'code': 'model._cron_model_copy(' + str(self.id) + ')'
            }
            cron = self.env['ir.cron'].sudo().create(cron_data)
            subscription.write({'cron_id': cron.id, 'state': 'running'})

    @api.model
    def _cron_model_copy(self, ids):
        self.browse(ids).model_copy()

    @api.multi
    def model_copy(self):
        for subscription in self.filtered(lambda sub: sub.cron_id):
            if not subscription.doc_source.exists():
                raise UserError(
                    _('Please provide another source document.\nThis one does not exist!'
                      ))

            default = {'state': 'draft'}
            documents = self.env['subscription.document'].search(
                [('model.model', '=', subscription.doc_source._name)], limit=1)
            fieldnames = dict(
                (f.field.name,
                 f.value == 'date' and fields.Date.today() or False)
                for f in documents.field_ids)
            default.update(fieldnames)

            # if there was only one remaining document to generate
            # the subscription is over and we mark it as being done
            if subscription.cron_id.numbercall == 1:
                subscription.write({'state': 'done'})
            else:
                subscription.write({'state': 'running'})
            copied_doc = subscription.doc_source.copy(default)
            self.env['subscription.subscription.history'].create({
                'subscription_id':
                subscription.id,
                'date':
                fields.Datetime.now(),
                'document_id':
                '%s,%s' % (subscription.doc_source._name, copied_doc.id)
            })

    @api.multi
    def unlink(self):
        if any(self.filtered(lambda s: s.state == "running")):
            raise UserError(_('You cannot delete an active subscription!'))
        return super(Subscription, self).unlink()

    @api.multi
    def set_done(self):
        self.mapped('cron_id').write({'active': False})
        self.write({'state': 'done'})

    @api.multi
    def set_draft(self):
        self.write({'state': 'draft'})
Example #18
0
class ImportSourceConsumerMixin(models.AbstractModel):
    """Source consumer mixin.

    Inheriting models can setup, configure and use import sources.

    Relation towards source records is generic to grant maximum freedom
    on which source type to use.
    """

    _name = "import.source.consumer.mixin"
    _description = "Import source consumer"

    source_id = fields.Integer(string="Source ID",
                               required=False,
                               ondelete="cascade")
    source_model = fields.Selection(string="Source type",
                                    selection="_selection_source_ref_id")
    source_ref_id = fields.Reference(
        string="Source",
        compute="_compute_source_ref_id",
        selection="_selection_source_ref_id",
        # NOTE: do not store a computed fields.Reference, Odoo crashes
        # with an error message "Mixing appels and orange..." when performing
        # a self.recompute() on such fields.
        store=False,
    )
    source_config_summary = fields.Html(
        compute="_compute_source_config_summary", readonly=True)

    @api.depends("source_model", "source_id")
    def _compute_source_ref_id(self):
        for item in self:
            item.source_ref_id = False
            if not item.source_id or not item.source_model:
                continue
            item.source_ref_id = "{0.source_model},{0.source_id}".format(item)

    @api.model
    def _selection_source_ref_id(self):
        return [("import.source.csv", "CSV"),
                ("import.source.csv.std", "Odoo CSV")]

    @api.depends("source_ref_id")
    def _compute_source_config_summary(self):
        for item in self:
            item.source_config_summary = False
            if not item.source_ref_id:
                continue
            item.source_config_summary = item.source_ref_id.config_summary

    def open_source_config(self):
        self.ensure_one()
        action = self.env[self.source_model].get_formview_action()
        action.update({
            "views":
            [(self.env[self.source_model].get_config_view_id(), "form")],
            "res_id":
            self.source_id,
            "target":
            "new",
        })
        return action

    def get_source(self):
        """Return the source to the consumer."""
        return self.source_ref_id
Example #19
0
class IrUiMenu(models.Model):
    _name = 'ir.ui.menu'
    _description = 'Menu'
    _order = "sequence,id"
    _parent_store = True

    def __init__(self, *args, **kwargs):
        super(IrUiMenu, self).__init__(*args, **kwargs)
        self.pool['ir.model.access'].register_cache_clearing_method(
            self._name, 'clear_caches')

    name = fields.Char(string='Menu', required=True, translate=True)
    active = fields.Boolean(default=True)
    sequence = fields.Integer(default=10)
    child_id = fields.One2many('ir.ui.menu', 'parent_id', string='Child IDs')
    parent_id = fields.Many2one('ir.ui.menu',
                                string='Parent Menu',
                                index=True,
                                ondelete="restrict")
    parent_path = fields.Char(index=True)
    groups_id = fields.Many2many('res.groups', 'ir_ui_menu_group_rel',
                                 'menu_id', 'gid', string='Groups',
                                 help="If you have groups, the visibility of this menu will be based on these groups. "\
                                      "If this field is empty, Odoo will compute visibility based on the related object's read access.")
    complete_name = fields.Char(string='Full Path',
                                compute='_compute_complete_name',
                                recursive=True)
    web_icon = fields.Char(string='Web Icon File')
    action = fields.Reference(selection=[(
        'ir.actions.report', 'ir.actions.report'
    ), ('ir.actions.act_window',
        'ir.actions.act_window'), (
            'ir.actions.act_url',
            'ir.actions.act_url'), (
                'ir.actions.server',
                'ir.actions.server'), ('ir.actions.client',
                                       'ir.actions.client')])

    web_icon_data = fields.Binary(string='Web Icon Image', attachment=True)

    @api.depends('name', 'parent_id.complete_name')
    def _compute_complete_name(self):
        for menu in self:
            menu.complete_name = menu._get_full_name()

    def _get_full_name(self, level=6):
        """ Return the full name of ``self`` (up to a certain level). """
        if level <= 0:
            return '...'
        if self.parent_id:
            return self.parent_id._get_full_name(
                level - 1) + MENU_ITEM_SEPARATOR + (self.name or "")
        else:
            return self.name

    def read_image(self, path):
        if not path:
            return False
        path_info = path.split(',')
        icon_path = get_module_resource(path_info[0], path_info[1])
        icon_image = False
        if icon_path:
            with tools.file_open(icon_path, 'rb') as icon_file:
                icon_image = base64.encodebytes(icon_file.read())
        return icon_image

    @api.constrains('parent_id')
    def _check_parent_id(self):
        if not self._check_recursion():
            raise ValidationError(
                _('Error! You cannot create recursive menus.'))

    @api.model
    @tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'debug')
    def _visible_menu_ids(self, debug=False):
        """ Return the ids of the menu items visible to the user. """
        # retrieve all menus, and determine which ones are visible
        context = {'ir.ui.menu.full_list': True}
        menus = self.with_context(context).search([]).sudo()

        groups = self.env.user.groups_id
        if not debug:
            groups = groups - self.env.ref('base.group_no_one')
        # first discard all menus with groups the user does not have
        menus = menus.filtered(
            lambda menu: not menu.groups_id or menu.groups_id & groups)

        # take apart menus that have an action
        action_menus = menus.filtered(lambda m: m.action and m.action.exists())
        folder_menus = menus - action_menus
        visible = self.browse()

        # process action menus, check whether their action is allowed
        access = self.env['ir.model.access']
        MODEL_GETTER = {
            'ir.actions.act_window': lambda action: action.res_model,
            'ir.actions.report': lambda action: action.model,
            'ir.actions.server': lambda action: action.model_id.model,
        }
        for menu in action_menus:
            get_model = MODEL_GETTER.get(menu.action._name)
            if not get_model or not get_model(menu.action) or \
                    access.check(get_model(menu.action), 'read', False):
                # make menu visible, and its folder ancestors, too
                visible += menu
                menu = menu.parent_id
                while menu and menu in folder_menus and menu not in visible:
                    visible += menu
                    menu = menu.parent_id

        return set(visible.ids)

    @api.returns('self')
    def _filter_visible_menus(self):
        """ Filter `self` to only keep the menu items that should be visible in
            the menu hierarchy of the current user.
            Uses a cache for speeding up the computation.
        """
        visible_ids = self._visible_menu_ids(
            request.session.debug if request else False)
        return self.filtered(lambda menu: menu.id in visible_ids)

    @api.model
    def _search(self,
                args,
                offset=0,
                limit=None,
                order=None,
                count=False,
                access_rights_uid=None):
        menu_ids = super(IrUiMenu,
                         self)._search(args,
                                       offset=0,
                                       limit=None,
                                       order=order,
                                       count=False,
                                       access_rights_uid=access_rights_uid)
        menus = self.browse(menu_ids)
        if menus:
            # menu filtering is done only on main menu tree, not other menu lists
            if not self._context.get('ir.ui.menu.full_list'):
                menus = menus._filter_visible_menus()
            if offset:
                menus = menus[offset:]
            if limit:
                menus = menus[:limit]
        return len(menus) if count else menus.ids

    def name_get(self):
        return [(menu.id, menu._get_full_name()) for menu in self]

    @api.model_create_multi
    def create(self, vals_list):
        self.clear_caches()
        for values in vals_list:
            if 'web_icon' in values:
                values['web_icon_data'] = self._compute_web_icon_data(
                    values.get('web_icon'))
        return super(IrUiMenu, self).create(vals_list)

    def write(self, values):
        self.clear_caches()
        if 'web_icon' in values:
            values['web_icon_data'] = self._compute_web_icon_data(
                values.get('web_icon'))
        return super(IrUiMenu, self).write(values)

    def _compute_web_icon_data(self, web_icon):
        """ Returns the image associated to `web_icon`.
            `web_icon` can either be:
              - an image icon [module, path]
              - a built icon [icon_class, icon_color, background_color]
            and it only has to call `read_image` if it's an image.
        """
        if web_icon and len(web_icon.split(',')) == 2:
            return self.read_image(web_icon)

    def unlink(self):
        # Detach children and promote them to top-level, because it would be unwise to
        # cascade-delete submenus blindly. We also can't use ondelete=set null because
        # that is not supported when _parent_store is used (would silently corrupt it).
        # TODO: ideally we should move them under a generic "Orphans" menu somewhere?
        extra = {'ir.ui.menu.full_list': True, 'active_test': False}
        direct_children = self.with_context(**extra).search([('parent_id',
                                                              'in', self.ids)])
        direct_children.write({'parent_id': False})

        self.clear_caches()
        return super(IrUiMenu, self).unlink()

    def copy(self, default=None):
        record = super(IrUiMenu, self).copy(default=default)
        match = NUMBER_PARENS.search(record.name)
        if match:
            next_num = int(match.group(1)) + 1
            record.name = NUMBER_PARENS.sub('(%d)' % next_num, record.name)
        else:
            record.name = record.name + '(1)'
        return record

    @api.model
    @api.returns('self')
    def get_user_roots(self):
        """ Return all root menu ids visible for the user.

        :return: the root menu ids
        :rtype: list(int)
        """
        return self.search([('parent_id', '=', False)])

    def _load_menus_blacklist(self):
        return []

    @api.model
    @tools.ormcache_context('self._uid', keys=('lang', ))
    def load_menus_root(self):
        fields = ['name', 'sequence', 'parent_id', 'action', 'web_icon_data']
        menu_roots = self.get_user_roots()
        menu_roots_data = menu_roots.read(fields) if menu_roots else []

        menu_root = {
            'id': False,
            'name': 'root',
            'parent_id': [-1, ''],
            'children': menu_roots_data,
            'all_menu_ids': menu_roots.ids,
        }

        xmlids = menu_roots._get_menuitems_xmlids()
        for menu in menu_roots_data:
            menu['xmlid'] = xmlids[menu['id']]

        return menu_root

    @api.model
    @tools.ormcache_context('self._uid', 'debug', keys=('lang', ))
    def load_menus(self, debug):
        """ Loads all menu items (all applications and their sub-menus).

        :return: the menu root
        :rtype: dict('children': menu_nodes)
        """
        fields = [
            'name', 'sequence', 'parent_id', 'action', 'web_icon',
            'web_icon_data'
        ]
        menu_roots = self.get_user_roots()
        menu_roots_data = menu_roots.read(fields) if menu_roots else []
        menu_root = {
            'id': False,
            'name': 'root',
            'parent_id': [-1, ''],
            'children': [menu['id'] for menu in menu_roots_data],
        }

        all_menus = {'root': menu_root}

        if not menu_roots_data:
            return all_menus

        # menus are loaded fully unlike a regular tree view, cause there are a
        # limited number of items (752 when all 6.1 addons are installed)
        menus_domain = [('id', 'child_of', menu_roots.ids)]
        blacklisted_menu_ids = self._load_menus_blacklist()
        if blacklisted_menu_ids:
            menus_domain = expression.AND(
                [menus_domain, [('id', 'not in', blacklisted_menu_ids)]])
        menus = self.search(menus_domain)
        menu_items = menus.read(fields)
        xmlids = (menu_roots + menus)._get_menuitems_xmlids()

        # add roots at the end of the sequence, so that they will overwrite
        # equivalent menu items from full menu read when put into id:item
        # mapping, resulting in children being correctly set on the roots.
        menu_items.extend(menu_roots_data)

        # set children ids and xmlids
        menu_items_map = {
            menu_item["id"]: menu_item
            for menu_item in menu_items
        }
        for menu_item in menu_items:
            menu_item.setdefault('children', [])
            parent = menu_item['parent_id'] and menu_item['parent_id'][0]
            menu_item['xmlid'] = xmlids.get(menu_item['id'], "")
            if parent in menu_items_map:
                menu_items_map[parent].setdefault('children',
                                                  []).append(menu_item['id'])
        all_menus.update(menu_items_map)

        # sort by sequence
        for menu_id in all_menus:
            all_menus[menu_id]['children'].sort(
                key=lambda id: all_menus[id]['sequence'])

        # recursively set app ids to related children
        def _set_app_id(app_id, menu):
            menu['app_id'] = app_id
            for child_id in menu['children']:
                _set_app_id(app_id, all_menus[child_id])

        for app in menu_roots_data:
            app_id = app['id']
            _set_app_id(app_id, all_menus[app_id])

        # filter out menus not related to an app (+ keep root menu)
        all_menus = {
            menu['id']: menu
            for menu in all_menus.values() if menu.get('app_id')
        }
        all_menus['root'] = menu_root

        return all_menus

    def _get_menuitems_xmlids(self):
        menuitems = self.env['ir.model.data'].sudo().search([
            ('res_id', 'in', self.ids), ('model', '=', 'ir.ui.menu')
        ])

        return {menu.res_id: menu.complete_name for menu in menuitems}
class MarketingCampaignTest(models.TransientModel):
    _name = 'marketing.campaign.test'
    _description = 'Marketing Campaign: Launch a Test'

    @api.model
    def default_get(self, default_fields):
        defaults = super(MarketingCampaignTest,
                         self).default_get(default_fields)
        if not defaults.get('res_id'):
            model_name = defaults.get('model_name')
            if not model_name and defaults.get('campaign_id'):
                model_name = self.env['marketing.campaign'].browse(
                    defaults['campaign_id']).model_name
            if model_name:
                resource = self.env[model_name].search([], limit=1)
                defaults['res_id'] = resource.id
        return defaults

    @api.model
    def _selection_target_model(self):
        models = self.env['ir.model'].search([('is_mail_thread', '=', True)])
        return [(model.model, model.name) for model in models]

    campaign_id = fields.Many2one('marketing.campaign',
                                  string='Campaign',
                                  required=True)
    model_id = fields.Many2one('ir.model',
                               string='Object',
                               related='campaign_id.model_id',
                               readonly=True)
    model_name = fields.Char('Record model',
                             related='campaign_id.model_id.model',
                             readonly=True)
    res_id = fields.Integer(string='Record ID', index=True)
    resource_ref = fields.Reference(string='Record',
                                    selection='_selection_target_model',
                                    compute='_compute_resource_ref',
                                    inverse='_set_resource_ref')

    @api.depends('model_name', 'res_id')
    def _compute_resource_ref(self):
        for participant in self:
            if participant.model_name:
                participant.resource_ref = '%s,%s' % (participant.model_name,
                                                      participant.res_id or 0)

    def _set_resource_ref(self):
        for participant in self:
            if participant.resource_ref:
                participant.res_id = participant.resource_ref.id

    def action_launch_test(self):
        """ Create test participant based on user choice. """
        participant = self.env['marketing.participant'].create({
            'campaign_id':
            self.campaign_id.id,
            'res_id':
            self.res_id,
            'is_test':
            True,
        })
        return {
            'name': _('Launch a Test'),
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'marketing.participant',
            'res_id': participant.id,
            'target': 'current',
        }
Example #21
0
class MarketingParticipant(models.Model):
    _name = 'marketing.participant'
    _description = 'Marketing Participant'
    _rec_name = 'resource_ref'

    @api.model
    def default_get(self, default_fields):
        defaults = super(MarketingParticipant,
                         self).default_get(default_fields)
        if 'res_id' in default_fields and not defaults.get('res_id'):
            model_name = defaults.get('model_name')
            if not model_name and defaults.get('campaign_id'):
                model_name = self.env['marketing.campaign'].browse(
                    defaults['campaign_id']).model_name
            if model_name and model_name in self.env:
                resource = self.env[model_name].search([], limit=1)
                defaults['res_id'] = resource.id
        return defaults

    @api.model
    def _selection_target_model(self):
        models = self.env['ir.model'].search([('is_mail_thread', '=', True)])
        return [(model.model, model.name) for model in models]

    def _search_resource_ref(self, operator, value):
        ir_models = set([
            model['model_name'] for model in
            self.env['marketing.campaign'].search([]).read(['model_name'])
        ])
        ir_model_ids = []
        for model in ir_models:
            if model in self.env:
                ir_model_ids += self.env['marketing.participant'].search([
                    '&', ('model_name', '=', model),
                    ('res_id', 'in', [
                        name[0]
                        for name in self.env[model].name_search(name=value)
                    ])
                ]).ids
        operator = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
        return [('id', operator, ir_model_ids)]

    campaign_id = fields.Many2one('marketing.campaign',
                                  string='Campaign',
                                  index=True,
                                  ondelete='cascade',
                                  required=True)
    model_id = fields.Many2one('ir.model',
                               string='Model',
                               related='campaign_id.model_id',
                               index=True,
                               readonly=True,
                               store=True)
    model_name = fields.Char(string='Record model',
                             related='campaign_id.model_id.model',
                             readonly=True,
                             store=True)
    res_id = fields.Integer(string='Record ID', index=True)
    resource_ref = fields.Reference(string='Record',
                                    selection='_selection_target_model',
                                    compute='_compute_resource_ref',
                                    inverse='_set_resource_ref',
                                    search='_search_resource_ref')
    trace_ids = fields.One2many('marketing.trace',
                                'participant_id',
                                string='Actions')
    state = fields.Selection(
        [
            ('running', 'Running'),
            ('completed', 'Completed'),
            ('unlinked', 'Removed'),
        ],
        default='running',
        index=True,
        required=True,
        help='Removed means the related record does not exist anymore.')
    is_test = fields.Boolean('Test Record', default=False)

    @api.depends('model_name', 'res_id')
    def _compute_resource_ref(self):
        for participant in self:
            if participant.model_name and participant.model_name in self.env:
                participant.resource_ref = '%s,%s' % (participant.model_name,
                                                      participant.res_id or 0)
            else:
                participant.resource_ref = None

    def _set_resource_ref(self):
        for participant in self:
            if participant.resource_ref:
                participant.res_id = participant.resource_ref.id

    def check_completed(self):
        existing_traces = self.env['marketing.trace'].search([
            ('participant_id', 'in', self.ids),
            ('state', '=', 'scheduled'),
        ])
        (self - existing_traces.mapped('participant_id')).write(
            {'state': 'completed'})

    @api.model
    def create(self, values):
        res = super(MarketingParticipant, self).create(values)
        # prepare first traces related to begin activities
        primary_activities = res.campaign_id.marketing_activity_ids.filtered(
            lambda act: act.trigger_type == 'begin')
        now = Datetime.now()
        trace_ids = [(0, 0, {
            'activity_id':
            activity.id,
            'schedule_date':
            now + relativedelta(
                **{activity.interval_type: activity.interval_number}),
        }) for activity in primary_activities]
        res.write({'trace_ids': trace_ids})
        return res

    def action_set_completed(self):
        ''' Manually mark as a completed and cancel every scheduled trace '''
        # TDE TODO: delegate set Canceled to trace record
        self.write({'state': 'completed'})
        self.env['marketing.trace'].search([('participant_id', 'in', self.ids),
                                            ('state', '=', 'scheduled')
                                            ]).write({
                                                'state':
                                                'canceled',
                                                'schedule_date':
                                                Datetime.now(),
                                                'state_msg':
                                                _('Marked as completed')
                                            })

    def action_set_running(self):
        self.write({'state': 'running'})

    def action_set_unlink(self):
        self.write({'state': 'unlinked'})
        self.env['marketing.trace'].search([('participant_id', 'in', self.ids),
                                            ('state', '=', 'scheduled')
                                            ]).write({
                                                'state':
                                                'canceled',
                                                'state_msg':
                                                _('Record deleted'),
                                            })
        return True
class SeBackend(models.Model):

    _name = 'se.backend'
    _description = 'Se Backend'
    _inherit = ['connector.backend', 'keychain.backend']
    _backend_type = 'se'
    _backend_name = 'search_engine_backend'

    # We can't leave this field required in database, because of strange
    # behaviors with name field defined already on connector.backend, which is
    # inherited by both se.backend and se.backend.spec.abstract.
    # In next version, the field is removed from connector.backend, so we
    # should probably just define the field here, with a required=True and
    # remove the related field defined on specific backend which will
    # be useless because it will be takend from se.backend...
    name = fields.Char(required=False)

    specific_model = fields.Selection(
        string='Type',
        selection=[],
        required=True,
        readonly=True
    )
    index_ids = fields.One2many(
        'se.index',
        'backend_id',
    )
    specific_backend = fields.Reference(
        string='Specific backend',
        compute='_compute_specific_backend',
        selection='_get_specific_backend_selection',
        readonly=True,
    )

    @api.model
    def _get_specific_backend_selection(self):
        spec_backend_selection = self._fields['specific_model'].selection
        vals = []
        s = self.with_context(active_test=False)
        for model, descr in spec_backend_selection:
            # We have to check if the table really exist.
            # Because in the case of the user uninstall a connector_XXX module
            # with a new se.backend (so who adds a new element into selection
            # field), no more se.backend will be available (because the
            # selection field still filled with the previous model and Odoo
            # try to load the model).
            if model in s.env and s.env[model]._table_exist():
                records = s.env[model].search([])
                for record in records:
                    vals.append((model, record.id))
        return vals

    @api.model
    def register_spec_backend(self, specific_backend_model):
        """
        This function must be called by specific backend from the
        _register_hook method to register it into the allowed specific models
        :param self:
        :param specific_backend_model:
        """
        self._fields['specific_model'].selection.append((
            specific_backend_model._name, specific_backend_model._description))

    @api.depends('specific_model')
    @api.multi
    def _compute_specific_backend(self):
        for specific_model in self.mapped('specific_model'):
            recs = self.filtered(lambda r, model=specific_model:
                                 r.specific_model == model)
            backends = self.env[specific_model].search([
                ('se_backend_id', 'in', recs.ids)])
            backend_by_rec = {r.se_backend_id: r for r in backends}
            for rec in recs:
                spec_backend = backend_by_rec.get(rec)
                rec.specific_backend = '%s,%s' % (
                    spec_backend._name, spec_backend.id)
Example #23
0
class PRTMailComposer(models.TransientModel):
    _inherit = 'mail.compose.message'
    _name = 'mail.compose.message'

    wizard_mode = fields.Char(string="Wizard mode")
    forward_ref = fields.Reference(string="Attach to record",
                                   selection='_referenceable_models_fwd',
                                   readonly=False)

    # -- Ref models
    @api.model
    def _referenceable_models_fwd(self):
        return [(x.object, x.name)
                for x in self.env['res.request.link'].sudo().search([])]

# -- Record ref change

    @api.onchange('forward_ref')
    @api.multi
    def ref_change(self):
        self.ensure_one()
        if self.forward_ref:
            self.update({
                'model': self.forward_ref._name,
                'res_id': self.forward_ref.id
            })

# -- Get record data

    @api.model
    def get_record_data(self, values):
        """
        Copy-pasted mail.compose.message original function so stay aware in case it is changed in Odoo core!

        Returns a defaults-like dict with initial values for the composition
        wizard when sending an email related a previous email (parent_id) or
        a document (model, res_id). This is based on previously computed default
        values. """
        result = {}
        subj = self._context.get('default_subject', False)
        subject = tools.ustr(subj) if subj else False
        if not subject:
            if values.get('parent_id'):
                parent = self.env['mail.message'].browse(
                    values.get('parent_id'))
                result['record_name'] = parent.record_name,
                subject = tools.ustr(parent.subject or parent.record_name
                                     or '')
                if not values.get('model'):
                    result['model'] = parent.model
                if not values.get('res_id'):
                    result['res_id'] = parent.res_id
                partner_ids = values.get('partner_ids', list()) +\
                              [(4, id) for id in parent.partner_ids.filtered(lambda rec: rec.email not in [self.env.user.email, self.env.user.company_id.email]).ids]
                if self._context.get(
                        'is_private'
                ) and parent.author_id:  # check message is private then add author also in partner list.
                    partner_ids += [(4, parent.author_id.id)]
                result['partner_ids'] = partner_ids
            elif values.get('model') and values.get('res_id'):
                doc_name_get = self.env[values.get('model')].browse(
                    values.get('res_id')).name_get()
                result[
                    'record_name'] = doc_name_get and doc_name_get[0][1] or ''
                subject = tools.ustr(result['record_name'])

            # Change prefix in case we are forwarding
            re_prefix = _('Fwd:') if self._context.get(
                'default_wizard_mode', False) == 'forward' else _('Re:')

            if subject and not (subject.startswith('Re:')
                                or subject.startswith(re_prefix)):
                subject = "%s %s" % (re_prefix, subject)

        result['subject'] = subject

        return result
Example #24
0
class app_menu(models.Model):
    _name = 'app.menu'
    _order = 'id desc'

    name = fields.Char(string=u"菜单名称", required=True)
    complete_name = fields.Char(string=u"完整路径",
                                compute="_compute_complete_name")
    sequence = fields.Integer(default=10, string=u"菜单顺序", help=u'数字越小优先级越高')
    parent_id = fields.Many2one(comodel_name='app.menu',
                                string=u'上级菜单',
                                default=None)
    action = fields.Reference(selection=[('app.act_window', 'APP窗口动作')],
                              default=None)
    icon = fields.Char(
        string=u"菜单图标",
        help=u'填写Awesome图标的名称,如fa-check,仅一级菜单有效。支持4.7.0版本的Awesome图标')
    groups_id = fields.Many2many('res.groups',
                                 'app_menu_group_rel',
                                 'app_menu_id',
                                 'gid',
                                 string='Groups',
                                 help=u"指定可访问该菜单的用户组列表,留空表示所有用户都可以访问")

    @api.depends('name', 'parent_id')
    def _compute_complete_name(self):
        for menu_item in self:
            complete_name = menu_item.name or ''
            parent_menu_item = menu_item.parent_id
            while parent_menu_item:
                complete_name = parent_menu_item.name + '/' + complete_name
                parent_menu_item = parent_menu_item.parent_id
            menu_item.complete_name = complete_name
        return 'test'

    @api.onchange('parent_id')
    def onchange_parent_id(self):
        if self.parent_id:
            self.icon = ''

    @api.model
    def get_user_menu_list(self):
        menus = self.search([('action', '!=', False)])
        menus_has_rights = []
        for menu_item in menus:
            try:
                self.env[menu_item.action.res_model].check_access_rights(
                    'read')
                menus_has_rights.append(menu_item)
            except:
                continue
        menus = []
        for menu_item in menus_has_rights:
            while menu_item:
                menu = {
                    'id':
                    menu_item.id,
                    'name':
                    menu_item.name,
                    'sequence':
                    menu_item.sequence,
                    'parent_id':
                    menu_item.parent_id.id,
                    'action':
                    self.env['app.act_window'].get_action_data(
                        menu_item.action and menu_item.action.id),
                    'icon':
                    menu_item.icon
                }
                if menu not in menus:
                    menus.append(menu)
                menu_item = menu_item.parent_id
        return menus
Example #25
0
class PrivacyLookupWizardLine(models.TransientModel):
    _name = 'privacy.lookup.wizard.line'
    _description = 'Privacy Lookup Wizard Line'
    _transient_max_count = 0
    _transient_max_hours = 24

    @api.model
    def _selection_target_model(self):
        return [(model.model, model.name)
                for model in self.env['ir.model'].sudo().search([])]

    wizard_id = fields.Many2one('privacy.lookup.wizard')
    res_id = fields.Integer(string="Resource ID", required=True)
    res_name = fields.Char(string='Resource name',
                           compute='_compute_res_name',
                           store=True)
    res_model_id = fields.Many2one('ir.model',
                                   'Related Document Model',
                                   ondelete='cascade')
    res_model = fields.Char(string='Document Model',
                            related='res_model_id.model',
                            store=True,
                            readonly=True)
    resource_ref = fields.Reference(string='Record',
                                    selection='_selection_target_model',
                                    compute='_compute_resource_ref',
                                    inverse='_set_resource_ref')
    has_active = fields.Boolean(compute='_compute_has_active', store=True)
    is_active = fields.Boolean()
    is_unlinked = fields.Boolean()
    execution_details = fields.Char(default='')

    @api.depends('res_model', 'res_id', 'is_unlinked')
    def _compute_resource_ref(self):
        for line in self:
            if line.res_model and line.res_model in self.env and not line.is_unlinked:
                # Exclude records that can't be read (eg: multi-company ir.rule)
                try:
                    self.env[line.res_model].browse(
                        line.res_id).check_access_rule('read')
                    line.resource_ref = '%s,%s' % (line.res_model, line.res_id
                                                   or 0)
                except Exception:
                    line.resource_ref = None
            else:
                line.resource_ref = None

    def _set_resource_ref(self):
        for line in self:
            if line.resource_ref:
                line.res_id = line.resource_ref.id

    @api.depends('res_model_id')
    def _compute_has_active(self):
        for line in self:
            if not line.res_model_id:
                line.has_active = False
                continue
            line.has_active = 'active' in self.env[line.res_model]

    @api.depends('res_model', 'res_id')
    def _compute_res_name(self):
        for line in self:
            if not line.res_id or not line.res_model:
                continue
            record = self.env[line.res_model].sudo().browse(line.res_id)
            if not record.exists():
                continue
            name = record.name_get()
            line.res_name = name[0][1] if name else ('%s/%s') % (
                line.res_model_id.name, line.res_id)

    @api.onchange('is_active')
    def _onchange_is_active(self):
        for line in self:
            if not line.res_model_id or not line.res_id:
                continue
            action = _('Unarchived') if line.is_active else _('Archived')
            line.execution_details = '%s %s #%s' % (
                action, line.res_model_id.name, line.res_id)
            self.env[line.res_model].sudo().browse(line.res_id).write(
                {'active': line.is_active})

    def action_unlink(self):
        self.ensure_one()
        if self.is_unlinked:
            raise UserError(_('The record is already unlinked.'))
        self.env[self.res_model].sudo().browse(self.res_id).unlink()
        self.execution_details = '%s %s #%s' % (
            _('Deleted'), self.res_model_id.name, self.res_id)
        self.is_unlinked = True

    def action_archive_all(self):
        for line in self:
            if not line.has_active or not line.is_active:
                continue
            line.is_active = False
            line._onchange_is_active()

    def action_unlink_all(self):
        for line in self:
            if line.is_unlinked:
                continue
            line.action_unlink()

    def action_open_record(self):
        self.ensure_one()
        return {
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_id': self.res_id,
            'res_model': self.res_model,
        }
Example #26
0
class EventMailScheduler(models.Model):
    """ Event automated mailing. This model replaces all existing fields and
    configuration allowing to send emails on events since Odoo 9. A cron exists
    that periodically checks for mailing to run. """
    _name = 'event.mail'
    _rec_name = 'event_id'
    _description = 'Event Automated Mailing'

    @api.model
    def _selection_template_model(self):
        return [('mail.template', 'Mail')]

    event_id = fields.Many2one('event.event',
                               string='Event',
                               required=True,
                               ondelete='cascade')
    sequence = fields.Integer('Display order')
    notification_type = fields.Selection([('mail', 'Mail')],
                                         string='Send',
                                         default='mail',
                                         required=True)
    interval_nbr = fields.Integer('Interval', default=1)
    interval_unit = fields.Selection([('now', 'Immediately'),
                                      ('hours', 'Hours'), ('days', 'Days'),
                                      ('weeks', 'Weeks'),
                                      ('months', 'Months')],
                                     string='Unit',
                                     default='hours',
                                     required=True)
    interval_type = fields.Selection([('after_sub', 'After each registration'),
                                      ('before_event', 'Before the event'),
                                      ('after_event', 'After the event')],
                                     string='Trigger ',
                                     default="before_event",
                                     required=True)
    scheduled_date = fields.Datetime('Schedule Date',
                                     compute='_compute_scheduled_date',
                                     store=True)
    # contact and status
    mail_registration_ids = fields.One2many(
        'event.mail.registration',
        'scheduler_id',
        help='Communication related to event registrations')
    mail_done = fields.Boolean("Sent", copy=False, readonly=True)
    mail_state = fields.Selection([('running', 'Running'),
                                   ('scheduled', 'Scheduled'),
                                   ('sent', 'Sent')],
                                  string='Global communication Status',
                                  compute='_compute_mail_state')
    mail_count_done = fields.Integer('# Sent', copy=False, readonly=True)
    template_model_id = fields.Many2one('ir.model',
                                        string='Template Model',
                                        compute='_compute_template_model_id',
                                        compute_sudo=True)
    template_ref = fields.Reference(string='Template',
                                    selection='_selection_template_model',
                                    required=True)

    @api.depends('notification_type')
    def _compute_template_model_id(self):
        mail_model = self.env['ir.model']._get('mail.template')
        for mail in self:
            mail.template_model_id = mail_model if mail.notification_type == 'mail' else False

    @api.depends('event_id.date_begin', 'event_id.date_end', 'interval_type',
                 'interval_unit', 'interval_nbr')
    def _compute_scheduled_date(self):
        for scheduler in self:
            if scheduler.interval_type == 'after_sub':
                date, sign = scheduler.event_id.create_date, 1
            elif scheduler.interval_type == 'before_event':
                date, sign = scheduler.event_id.date_begin, -1
            else:
                date, sign = scheduler.event_id.date_end, 1

            scheduler.scheduled_date = date + _INTERVALS[
                scheduler.interval_unit](
                    sign * scheduler.interval_nbr) if date else False

    @api.depends('interval_type', 'scheduled_date', 'mail_done')
    def _compute_mail_state(self):
        for scheduler in self:
            # registrations based
            if scheduler.interval_type == 'after_sub':
                scheduler.mail_state = 'running'
            # global event based
            elif scheduler.mail_done:
                scheduler.mail_state = 'sent'
            elif scheduler.scheduled_date:
                scheduler.mail_state = 'scheduled'
            else:
                scheduler.mail_state = 'running'

    def execute(self):
        for scheduler in self:
            now = fields.Datetime.now()
            if scheduler.interval_type == 'after_sub':
                new_registrations = scheduler.event_id.registration_ids.filtered_domain(
                    [('state', 'not in', ('cancel', 'draft'))
                     ]) - scheduler.mail_registration_ids.registration_id
                scheduler._create_missing_mail_registrations(new_registrations)

                # execute scheduler on registrations
                scheduler.mail_registration_ids.execute()
                total_sent = len(
                    scheduler.mail_registration_ids.filtered(
                        lambda reg: reg.mail_sent))
                scheduler.update({
                    'mail_done':
                    total_sent >= (scheduler.event_id.seats_reserved +
                                   scheduler.event_id.seats_used),
                    'mail_count_done':
                    total_sent,
                })
            else:
                # before or after event -> one shot email
                if scheduler.mail_done or scheduler.notification_type != 'mail':
                    continue
                # no template -> ill configured, skip and avoid crash
                if not scheduler.template_ref:
                    continue
                # do not send emails if the mailing was scheduled before the event but the event is over
                if scheduler.scheduled_date <= now and (
                        scheduler.interval_type != 'before_event'
                        or scheduler.event_id.date_end > now):
                    scheduler.event_id.mail_attendees(
                        scheduler.template_ref.id)
                    scheduler.update({
                        'mail_done':
                        True,
                        'mail_count_done':
                        scheduler.event_id.seats_reserved +
                        scheduler.event_id.seats_used,
                    })
        return True

    def _create_missing_mail_registrations(self, registrations):
        new = []
        for scheduler in self:
            new += [{
                'registration_id': registration.id,
                'scheduler_id': scheduler.id,
            } for registration in registrations]
        if new:
            return self.env['event.mail.registration'].create(new)
        return self.env['event.mail.registration']

    @api.model
    def _warn_template_error(self, scheduler, exception):
        # We warn ~ once by hour ~ instead of every 10 min if the interval unit is more than 'hours'.
        if random.random() < 0.1666 or scheduler.interval_unit in ('now',
                                                                   'hours'):
            ex_s = exception_to_unicode(exception)
            try:
                event, template = scheduler.event_id, scheduler.template_ref
                emails = list(
                    set([
                        event.organizer_id.email, event.user_id.email,
                        template.write_uid.email
                    ]))
                subject = _("WARNING: Event Scheduler Error for event: %s",
                            event.name)
                body = _("""Event Scheduler for:
  - Event: %(event_name)s (%(event_id)s)
  - Scheduled: %(date)s
  - Template: %(template_name)s (%(template_id)s)

Failed with error:
  - %(error)s

You receive this email because you are:
  - the organizer of the event,
  - or the responsible of the event,
  - or the last writer of the template.
""",
                         event_name=event.name,
                         event_id=event.id,
                         date=scheduler.scheduled_date,
                         template_name=template.name,
                         template_id=template.id,
                         error=ex_s)
                email = self.env['ir.mail_server'].build_email(
                    email_from=self.env.user.email,
                    email_to=emails,
                    subject=subject,
                    body=body,
                )
                self.env['ir.mail_server'].send_email(email)
            except Exception as e:
                _logger.error(
                    "Exception while sending traceback by email: %s.\n Original Traceback:\n%s",
                    e, exception)
                pass

    @api.model
    def run(self, autocommit=False):
        """ Backward compatible method, notably if crons are not updated when
        migrating for some reason. """
        return self.schedule_communications(autocommit=autocommit)

    @api.model
    def schedule_communications(self, autocommit=False):
        schedulers = self.search([('mail_done', '=', False),
                                  ('scheduled_date', '<=',
                                   fields.Datetime.now())])

        for scheduler in schedulers:
            try:
                # Prevent a mega prefetch of the registration ids of all the events of all the schedulers
                self.browse(scheduler.id).execute()
            except Exception as e:
                _logger.exception(e)
                self.invalidate_cache()
                self._warn_template_error(scheduler, e)
            else:
                if autocommit and not getattr(threading.currentThread(),
                                              'testing', False):
                    self.env.cr.commit()
        return True
Example #27
0
class CrmClaim(models.Model):
    """ Crm claim
    """
    _name = "crm.claim"
    _description = "Claim"
    _order = "priority,date desc"
    _inherit = ['mail.thread']

    @api.model
    def _get_default_stage_id(self):
        """ Gives default stage_id """
        team_id = self.env['crm.team']._get_default_team_id()
        return self.stage_find(team_id.id, [('sequence', '=', '1')])

    @api.model
    def _get_default_team(self):
        return self.env['crm.team']._get_default_team_id()

    name = fields.Char(
        string='Claim Subject',
        required=True,
    )
    active = fields.Boolean(default=True, )
    action_next = fields.Char(string='Next Action', )
    date_action_next = fields.Datetime(string='Next Action Date', )
    description = fields.Text()
    resolution = fields.Text()
    create_date = fields.Datetime(
        string='Creation Date',
        readonly=True,
    )
    write_date = fields.Datetime(
        string='Update Date',
        readonly=True,
    )
    date_deadline = fields.Date(string='Deadline', )
    date_closed = fields.Datetime(
        string='Closed',
        readonly=True,
    )
    date = fields.Datetime(
        string='Claim Date',
        index=True,
        detault=fields.Datetime.now,
    )
    model_ref_id = fields.Reference(
        selection=referenceable_models,
        string='Reference',
        oldname='ref',
    )
    categ_id = fields.Many2one(
        comodel_name='crm.claim.category',
        string='Category',
    )
    priority = fields.Selection(
        selection=[
            ('0', 'Low'),
            ('1', 'Normal'),
            ('2', 'High'),
        ],
        default='1',
    )
    type_action = fields.Selection(
        selection=[
            ('correction', 'Corrective Action'),
            ('prevention', 'Preventive Action'),
        ],
        string='Action Type',
    )
    user_id = fields.Many2one(
        comodel_name='res.users',
        string='Responsible',
        track_visibility='always',
        default=lambda self: self.env.user,
    )
    user_fault = fields.Char(string='Trouble Responsible', )
    team_id = fields.Many2one(
        comodel_name='crm.team',
        string='Sales Team',
        index=True,
        default=_get_default_team,
        help="Responsible sales team. Define Responsible user and Email "
        "account for mail gateway.",
    )
    company_id = fields.Many2one(
        comodel_name='res.company',
        string='Company',
        default=lambda self: self.env.user.company_id,
    )
    partner_id = fields.Many2one(
        comodel_name='res.partner',
        string='Partner',
    )
    email_cc = fields.Text(
        string='Watchers Emails',
        help="These email addresses will be added to the CC field of all "
        "inbound and outbound emails for this record before being sent. "
        "Separate multiple email addresses with a comma",
    )
    email_from = fields.Char(
        string='Email',
        help="Destination email for email gateway.",
    )
    partner_phone = fields.Char(string='Phone', )
    stage_id = fields.Many2one(
        comodel_name='crm.claim.stage',
        string='Stage',
        track_visibility='onchange',
        default=_get_default_stage_id,
        domain="['|', ('team_ids', '=', team_id), ('case_default', '=', True)]"
    )
    cause = fields.Text(string='Root Cause', )

    def stage_find(self, team_id, domain=None, order='sequence'):
        """ Override of the base.stage method
            Parameter of the stage search taken from the lead:
            - team_id: if set, stages must belong to this team or
              be a default case
        """
        if domain is None:  # pragma: no cover
            domain = []
        # collect all team_ids
        team_ids = []
        if team_id:
            team_ids.append(team_id)
        team_ids.extend(self.mapped('team_id').ids)
        search_domain = []
        if team_ids:
            search_domain += ['|'] * len(team_ids)
            for team_id in team_ids:
                search_domain.append(('team_ids', '=', team_id))
        search_domain.append(('case_default', '=', True))
        # AND with the domain in parameter
        search_domain += list(domain)
        # perform search, return the first found
        return self.env['crm.claim.stage'].search(search_domain,
                                                  order=order,
                                                  limit=1).id

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        """This function returns value of partner address based on partner
           :param email: ignored
        """
        if self.partner_id:
            self.email_from = self.partner_id.email
            self.partner_phone = self.partner_id.phone

    @api.onchange('categ_id')
    def onchange_categ_id(self):
        if self.stage_id:
            self.team_id = self.categ_id.team_id

    @api.model
    def create(self, values):
        ctx = self.env.context.copy()
        if values.get('team_id') and not ctx.get('default_team_id'):
            ctx['default_team_id'] = values.get('team_id')
        return super(CrmClaim, self.with_context(context=ctx)).create(values)

    @api.multi
    def copy(self, default=None):
        default = dict(
            default or {},
            stage_id=self._get_default_stage_id(),
            name=_('%s (copy)') % self.name,
        )
        return super(CrmClaim, self).copy(default)

    # -------------------------------------------------------
    # Mail gateway
    # -------------------------------------------------------
    @api.model
    def message_new(self, msg, custom_values=None):
        """ Overrides mail_thread message_new that is called by the mailgateway
            through message_process.
            This override updates the document according to the email.
        """
        if custom_values is None:
            custom_values = {}
        desc = html2plaintext(msg.get('body')) if msg.get('body') else ''
        defaults = {
            'name': msg.get('subject') or _("No Subject"),
            'description': desc,
            'email_from': msg.get('from'),
            'email_cc': msg.get('cc'),
            'partner_id': msg.get('author_id', False),
        }
        if msg.get('priority'):
            defaults['priority'] = msg.get('priority')
        defaults.update(custom_values)
        return super(CrmClaim, self).message_new(msg, custom_values=defaults)
Example #28
0
class DmsSecurityMixin(models.AbstractModel):
    _name = "dms.security.mixin"
    _description = "DMS Security Mixin"

    # Submodels must define this field that points to the owner dms.directory
    _directory_field = "directory_id"

    res_model = fields.Char(string="Linked attachments model", index=True, store=True)
    res_id = fields.Integer(
        string="Linked attachments record ID", index=True, store=True
    )
    record_ref = fields.Reference(
        string="Record Referenced",
        compute="_compute_record_ref",
        selection=lambda self: self._get_ref_selection(),
    )
    permission_read = fields.Boolean(
        compute="_compute_permissions",
        search="_search_permission_read",
        string="Read Access",
    )
    permission_create = fields.Boolean(
        compute="_compute_permissions",
        search="_search_permission_create",
        string="Create Access",
    )
    permission_write = fields.Boolean(
        compute="_compute_permissions",
        search="_search_permission_write",
        string="Write Access",
    )
    permission_unlink = fields.Boolean(
        compute="_compute_permissions",
        search="_search_permission_unlink",
        string="Delete Access",
    )

    @api.model
    def _get_ref_selection(self):
        models = self.env["ir.model"].search([])
        return [(model.model, model.name) for model in models]

    @api.depends("res_model", "res_id")
    def _compute_record_ref(self):
        for record in self:
            record.record_ref = False
            if record.res_model and record.res_id:
                record.record_ref = "{},{}".format(record.res_model, record.res_id)

    def _compute_permissions(self):
        """Get permissions for the current record.

        âš  Not very performant; only display field on form views.
        """
        # Superuser unrestricted 🦸
        if self.env.su:
            self.update(
                {
                    "permission_create": True,
                    "permission_read": True,
                    "permission_unlink": True,
                    "permission_write": True,
                }
            )
            return
        # Update according to presence when applying ir.rule
        creatable = self._filter_access_rules("create")
        readable = self._filter_access_rules("read")
        unlinkable = self._filter_access_rules("unlink")
        writeable = self._filter_access_rules("write")
        for one in self:
            one.update(
                {
                    "permission_create": bool(one & creatable),
                    "permission_read": bool(one & readable),
                    "permission_unlink": bool(one & unlinkable),
                    "permission_write": bool(one & writeable),
                }
            )

    @api.model
    def _get_domain_by_inheritance(self, operation):
        """Get domain for inherited accessible records."""
        if self.env.su:
            return []
        inherited_access_field = "storage_id_inherit_access_from_parent_record"
        if self._name != "dms.directory":
            inherited_access_field = "{}.{}".format(
                self._directory_field, inherited_access_field,
            )
        inherited_access_domain = [(inherited_access_field, "=", True)]
        domains = []
        # Get all used related records
        related_groups = self.sudo().read_group(
            domain=inherited_access_domain + [("res_model", "!=", False)],
            fields=["res_id:array_agg"],
            groupby=["res_model"],
        )
        for group in related_groups:
            try:
                model = self.env[group["res_model"]]
            except KeyError:
                # Model not registered. This is normal if you are upgrading the
                # database. Otherwise, you probably have garbage DMS data.
                # These records will be accessible by DB users only.
                domains.append(
                    [
                        ("res_model", "=", group["res_model"]),
                        (True, "=", self.env.user.has_group("base.group_user")),
                    ]
                )
                continue
            # Check model access only once per batch
            if not model.check_access_rights(operation, raise_exception=False):
                continue
            domains.append([("res_model", "=", model._name), ("res_id", "=", False)])
            # Check record access in batch too
            group_ids = [i for i in group["res_id"] if i]  # Hack to remove None res_id
            related_ok = model.browse(group_ids)._filter_access_rules_python(operation)
            if not related_ok:
                continue
            domains.append(
                [("res_model", "=", model._name), ("res_id", "in", related_ok.ids)]
            )
        result = inherited_access_domain + OR(domains)
        return result

    @api.model
    def _get_access_groups_query(self, operation):
        """Return the query to select access groups."""
        operation_check = {
            "create": "AND dag.perm_inclusive_create",
            "read": "",
            "unlink": "AND dag.perm_inclusive_unlink",
            "write": "AND dag.perm_inclusive_write",
        }[operation]
        select = """
            SELECT
                dir_group_rel.aid
            FROM
                dms_directory_complete_groups_rel AS dir_group_rel
                INNER JOIN dms_access_group AS dag
                    ON dir_group_rel.gid = dag.id
                INNER JOIN dms_access_group_users_rel AS users
                    ON users.gid = dag.id
            WHERE
                users.uid = %s {}
            """.format(
            operation_check
        )
        return (select, (self.env.uid,))

    @api.model
    def _get_domain_by_access_groups(self, operation):
        """Get domain for records accessible applying DMS access groups."""
        result = [
            (
                "%s.storage_id_inherit_access_from_parent_record"
                % self._directory_field,
                "=",
                False,
            ),
            (
                self._directory_field,
                "inselect",
                self._get_access_groups_query(operation),
            ),
        ]
        return result

    @api.model
    def _get_permission_domain(self, operator, value, operation):
        """Abstract logic for searching computed permission fields."""
        _self = self
        # HACK ir.rule domain is always computed with sudo, so if this check is
        # true, we can assume safely that you're checking permissions
        if self.env.su and value == self.env.uid:
            _self = self.sudo(False)
            value = bool(value)
        # Tricky one, to know if you want to search
        # positive or negative access
        positive = (operator not in NEGATIVE_TERM_OPERATORS) == bool(value)
        if _self.env.su:
            # You're SUPERUSER_ID
            return TRUE_DOMAIN if positive else FALSE_DOMAIN
        # Obtain and combine domains
        result = OR(
            [
                _self._get_domain_by_access_groups(operation),
                _self._get_domain_by_inheritance(operation),
            ]
        )
        if not positive:
            result.insert(0, "!")
        return result

    @api.model
    def _search_permission_create(self, operator, value):
        return self._get_permission_domain(operator, value, "create")

    @api.model
    def _search_permission_read(self, operator, value):
        return self._get_permission_domain(operator, value, "read")

    @api.model
    def _search_permission_unlink(self, operator, value):
        return self._get_permission_domain(operator, value, "unlink")

    @api.model
    def _search_permission_write(self, operator, value):
        return self._get_permission_domain(operator, value, "write")

    def _filter_access_rules_python(self, operation):
        # Only kept to not break inheritance; see next comment
        result = super()._filter_access_rules_python(operation)
        # HACK Always fall back to applying rules by SQL.
        # Upstream `_filter_acccess_rules_python()` doesn't use computed fields
        # search methods. Thus, it will take the `[('permission_{operation}',
        # '=', user.id)]` rule literally. Obviously that will always fail
        # because `self[f"permission_{operation}"]` will always be a `bool`,
        # while `user.id` will always be an `int`.
        result |= self._filter_access_rules(operation)
        return result

    @api.model_create_multi
    def create(self, vals_list):
        # Create as sudo to avoid testing creation permissions before DMS security
        # groups are attached (otherwise nobody would be able to create)
        res = super(DmsSecurityMixin, self.sudo()).create(vals_list)
        # Need to flush now, so all groups are stored in DB and the SELECT used
        # to check access works
        res.flush()
        # Go back to original sudo state and check we really had creation permission
        res = res.sudo(self.env.su)
        res.check_access_rights("create")
        res.check_access_rule("create")
        return res
Example #29
0
class Rating(models.Model):
    _name = "rating.rating"
    _description = "Rating"
    _order = 'write_date desc'
    _rec_name = 'res_name'
    _sql_constraints = [
        ('rating_range', 'check(rating >= 0 and rating <= 5)',
         'Rating should be between 0 and 5'),
    ]

    @api.depends('res_model', 'res_id')
    def _compute_res_name(self):
        for rating in self:
            name = self.env[rating.res_model].sudo().browse(
                rating.res_id).name_get()
            rating.res_name = name and name[0][1] or ('%s/%s') % (
                rating.res_model, rating.res_id)

    @api.model
    def _default_access_token(self):
        return uuid.uuid4().hex

    @api.model
    def _selection_target_model(self):
        return [(model.model, model.name)
                for model in self.env['ir.model'].sudo().search([])]

    create_date = fields.Datetime(string="Submitted on")
    res_name = fields.Char(string='Resource name',
                           compute='_compute_res_name',
                           store=True,
                           help="The name of the rated resource.")
    res_model_id = fields.Many2one('ir.model',
                                   'Related Document Model',
                                   index=True,
                                   ondelete='cascade',
                                   help='Model of the followed resource')
    res_model = fields.Char(string='Document Model',
                            related='res_model_id.model',
                            store=True,
                            index=True,
                            readonly=True)
    res_id = fields.Integer(string='Document',
                            required=True,
                            help="Identifier of the rated object",
                            index=True)
    resource_ref = fields.Reference(string='Resource Ref',
                                    selection='_selection_target_model',
                                    compute='_compute_resource_ref',
                                    readonly=True)
    parent_res_name = fields.Char('Parent Document Name',
                                  compute='_compute_parent_res_name',
                                  store=True)
    parent_res_model_id = fields.Many2one('ir.model',
                                          'Parent Related Document Model',
                                          index=True,
                                          ondelete='cascade')
    parent_res_model = fields.Char('Parent Document Model',
                                   store=True,
                                   related='parent_res_model_id.model',
                                   index=True,
                                   readonly=False)
    parent_res_id = fields.Integer('Parent Document', index=True)
    parent_ref = fields.Reference(string='Parent Ref',
                                  selection='_selection_target_model',
                                  compute='_compute_parent_ref',
                                  readonly=True)
    rated_partner_id = fields.Many2one('res.partner',
                                       string="Rated Operator",
                                       help="Owner of the rated resource")
    rated_partner_name = fields.Char(related="rated_partner_id.name")
    partner_id = fields.Many2one('res.partner',
                                 string='Customer',
                                 help="Author of the rating")
    rating = fields.Float(string="Rating Value",
                          group_operator="avg",
                          default=0,
                          help="Rating value: 0=Unhappy, 5=Happy")
    rating_image = fields.Binary('Image', compute='_compute_rating_image')
    rating_text = fields.Selection([('top', 'Satisfied'), ('ok', 'Okay'),
                                    ('ko', 'Dissatisfied'),
                                    ('none', 'No Rating yet')],
                                   string='Rating',
                                   store=True,
                                   compute='_compute_rating_text',
                                   readonly=True)
    feedback = fields.Text('Comment', help="Reason of the rating")
    message_id = fields.Many2one(
        'mail.message',
        string="Message",
        index=True,
        ondelete='cascade',
        help=
        "Associated message when posting a review. Mainly used in website addons."
    )
    is_internal = fields.Boolean('Visible Internally Only',
                                 readonly=False,
                                 related='message_id.is_internal',
                                 store=True)
    access_token = fields.Char(
        'Security Token',
        default=_default_access_token,
        help="Access token to set the rating of the value")
    consumed = fields.Boolean(string="Filled Rating",
                              help="Enabled if the rating has been filled.")

    @api.depends('res_model', 'res_id')
    def _compute_resource_ref(self):
        for rating in self:
            if rating.res_model and rating.res_model in self.env:
                rating.resource_ref = '%s,%s' % (rating.res_model,
                                                 rating.res_id or 0)
            else:
                rating.resource_ref = None

    @api.depends('parent_res_model', 'parent_res_id')
    def _compute_parent_ref(self):
        for rating in self:
            if rating.parent_res_model and rating.parent_res_model in self.env:
                rating.parent_ref = '%s,%s' % (rating.parent_res_model,
                                               rating.parent_res_id or 0)
            else:
                rating.parent_ref = None

    @api.depends('parent_res_model', 'parent_res_id')
    def _compute_parent_res_name(self):
        for rating in self:
            name = False
            if rating.parent_res_model and rating.parent_res_id:
                name = self.env[rating.parent_res_model].sudo().browse(
                    rating.parent_res_id).name_get()
                name = name and name[0][1] or ('%s/%s') % (
                    rating.parent_res_model, rating.parent_res_id)
            rating.parent_res_name = name

    def _get_rating_image_filename(self):
        self.ensure_one()
        if self.rating >= RATING_LIMIT_SATISFIED:
            rating_int = 5
        elif self.rating >= RATING_LIMIT_OK:
            rating_int = 3
        elif self.rating >= RATING_LIMIT_MIN:
            rating_int = 1
        else:
            rating_int = 0
        return 'rating_%s.png' % rating_int

    def _compute_rating_image(self):
        for rating in self:
            try:
                image_path = get_resource_path(
                    'rating', 'static/src/img',
                    rating._get_rating_image_filename())
                rating.rating_image = base64.b64encode(
                    open(image_path, 'rb').read()) if image_path else False
            except (IOError, OSError):
                rating.rating_image = False

    @api.depends('rating')
    def _compute_rating_text(self):
        for rating in self:
            if rating.rating >= RATING_LIMIT_SATISFIED:
                rating.rating_text = 'top'
            elif rating.rating >= RATING_LIMIT_OK:
                rating.rating_text = 'ok'
            elif rating.rating >= RATING_LIMIT_MIN:
                rating.rating_text = 'ko'
            else:
                rating.rating_text = 'none'

    @api.model_create_multi
    def create(self, vals_list):
        for values in vals_list:
            if values.get('res_model_id') and values.get('res_id'):
                values.update(self._find_parent_data(values))
        return super().create(vals_list)

    def write(self, values):
        if values.get('res_model_id') and values.get('res_id'):
            values.update(self._find_parent_data(values))
        return super(Rating, self).write(values)

    def unlink(self):
        # OPW-2181568: Delete the chatter message too
        self.env['mail.message'].search([('rating_ids', 'in', self.ids)
                                         ]).unlink()
        return super(Rating, self).unlink()

    def _find_parent_data(self, values):
        """ Determine the parent res_model/res_id, based on the values to create or write """
        current_model_name = self.env['ir.model'].sudo().browse(
            values['res_model_id']).model
        current_record = self.env[current_model_name].browse(values['res_id'])
        data = {
            'parent_res_model_id': False,
            'parent_res_id': False,
        }
        if hasattr(current_record, '_rating_get_parent_field_name'):
            current_record_parent = current_record._rating_get_parent_field_name(
            )
            if current_record_parent:
                parent_res_model = getattr(current_record,
                                           current_record_parent)
                data['parent_res_model_id'] = self.env['ir.model']._get(
                    parent_res_model._name).id
                data['parent_res_id'] = parent_res_model.id
        return data

    def reset(self):
        for record in self:
            record.write({
                'rating': 0,
                'access_token': record._default_access_token(),
                'feedback': False,
                'consumed': False,
            })

    def action_open_rated_object(self):
        self.ensure_one()
        return {
            'type': 'ir.actions.act_window',
            'res_model': self.res_model,
            'res_id': self.res_id,
            'views': [[False, 'form']]
        }
Example #30
0
class AccountCheckOperation(models.Model):

    _name = 'account.check.operation'
    _rec_name = 'operation'
    _order = 'date desc, id desc'
    # _order = 'create_date desc'

    # al final usamos solo date y no datetime porque el otro dato ya lo tenemos
    # en create_date. ademas el orden es una mezcla de la fecha el id
    # y entonces la fecha la solemos computar con el payment date para que
    # sea igual a la fecha contable (payment date va al asiento)
    # date = fields.Datetime(
    date = fields.Date(
        default=fields.Date.context_today,
        # default=lambda self: fields.Datetime.now(),
        required=True,
        index=True,
    )
    check_id = fields.Many2one(
        'account.check',
        'Check',
        required=True,
        ondelete='cascade',
        auto_join=True,
        index=True,
    )
    operation = fields.Selection(
        [
            # from payments
            ('holding', 'Receive'),
            ('deposited', 'Deposit'),
            ('selled', 'Sell'),
            ('delivered', 'Deliver'),
            # usado para hacer transferencias internas, es lo mismo que delivered
            # (endosado) pero no queremos confundir con terminos, a la larga lo
            # volvemos a poner en holding
            ('transfered', 'Transfer'),
            ('handed', 'Hand'),
            ('withdrawed', 'Withdrawal'),
            # from checks
            ('reclaimed', 'Claim'),
            ('rejected', 'Rejection'),
            ('debited', 'Debit'),
            ('returned', 'Return'),
            # al final no vamos a implemnetar esto ya que habria que hacer muchas
            # cosas hasta actualizar el asiento, mejor se vuelve atras y se
            # vuelve a generar deuda y listo, igualmente lo dejamos por si se
            # quiere usar de manera manual
            ('changed', 'Change'),
            ('cancel', 'Cancel'),
        ],
        required=True,
        index=True,
    )
    origin_name = fields.Char(compute='_compute_origin_name')
    origin = fields.Reference(string='Origin Document',
                              selection='_reference_models')
    partner_id = fields.Many2one(
        'res.partner',
        string='Partner',
    )
    notes = fields.Text()

    @api.multi
    def unlink(self):
        for rec in self:
            if rec.origin:
                raise ValidationError(
                    _('You can not delete a check operation that has an origin.'
                      '\nYou can delete the origin reference and unlink after.'
                      ))
        return super(AccountCheckOperation, self).unlink()

    @api.multi
    @api.depends('origin')
    def _compute_origin_name(self):
        """
        We add this computed method because an error on tree view displaying
        reference field when destiny record is deleted.
        As said in this post (last answer) we should use name_get instead of
        display_name
        https://www.odoo.com/es_ES/forum/ayuda-1/question/
        how-to-override-name-get-method-in-new-api-61228
        """
        for rec in self:
            try:
                if rec.origin:
                    id, name = rec.origin.name_get()[0]
                    origin_name = name
                    # origin_name = rec.origin.display_name
                else:
                    origin_name = False
            except Exception as e:
                _logger.exception("Compute origin on checks exception: %s" % e)
                # if we can get origin we clean it
                rec.write({'origin': False})
                origin_name = False
            rec.origin_name = origin_name

    @api.model
    def _reference_models(self):
        return [
            ('account.payment', 'Payment'),
            ('account.check', 'Check'),
            ('account.invoice', 'Invoice'),
            ('account.move', 'Journal Entry'),
            ('account.move.line', 'Journal Item'),
            ('account.bank.statement.line', 'Statement Line'),
        ]