예제 #1
0
파일: test_related.py 프로젝트: 7Gates/odoo
    def test_00_sanitize(self):
        cr, uid, context = self.cr, self.uid, {}
        old_columns = self.partner._columns
        self.partner._columns = dict(old_columns)
        self.partner._columns.update({
            'comment': fields.html('Secure Html', sanitize=False),
        })
        some_ugly_html = """<p>Oops this should maybe be sanitized
% if object.some_field and not object.oriented:
<table>
    % if object.other_field:
    <tr style="border: 10px solid black;">
        ${object.mako_thing}
        <td>
    </tr>
    % endif
    <tr>
%if object.dummy_field:
        <p>Youpie</p>
%endif"""

        pid = self.partner.create(cr, uid, {
            'name': 'Raoul Poilvache',
            'comment': some_ugly_html,
        }, context=context)
        partner = self.partner.browse(cr, uid, pid, context=context)
        self.assertEqual(partner.comment, some_ugly_html, 'Error in HTML field: content was sanitized but field has sanitize=False')

        self.partner._columns.update({
            'comment': fields.html('Unsecure Html', sanitize=True),
        })
        self.partner.write(cr, uid, [pid], {
            'comment': some_ugly_html,
        }, context=context)
        partner = self.partner.browse(cr, uid, pid, context=context)
        # sanitize should have closed tags left open in the original html
        self.assertIn('</table>', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
        self.assertIn('</td>', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
        self.assertIn('<tr style="', partner.comment, 'Style attr should not have been stripped')

        self.partner._columns['comment'] = fields.html('Stripped Html', sanitize=True, strip_style=True)
        self.partner.write(cr, uid, [pid], {'comment': some_ugly_html}, context=context)
        partner = self.partner.browse(cr, uid, pid, context=context)
        self.assertNotIn('<tr style="', partner.comment, 'Style attr should have been stripped')

        self.partner._columns = old_columns
예제 #2
0
파일: res_users.py 프로젝트: sc4you/odoo-2
class res_users(osv.osv):
    """ User class. A res.users record models an OpenERP user and is different
        from an employee.

        res.users class now inherits from res.partner. The partner model is
        used to store the data related to the partner: lang, name, address,
        avatar, ... The user model is now dedicated to technical data.
    """
    __admin_ids = {}
    _uid_cache = {}
    _inherits = {
        'res.partner': 'partner_id',
    }
    _name = "res.users"
    _description = 'Users'

    def _set_new_password(self, cr, uid, id, name, value, args, context=None):
        if value is False:
            # Do not update the password if no value is provided, ignore silently.
            # For example web client submits False values for all empty fields.
            return
        if uid == id:
            # To change their own password users must use the client-specific change password wizard,
            # so that the new password is immediately used for further RPC requests, otherwise the user
            # will face unexpected 'Access Denied' exceptions.
            raise UserError(_('Please use the change password wizard (in User Preferences or User menu) to change your own password.'))
        self.write(cr, uid, id, {'password': value})

    def _get_password(self, cr, uid, ids, arg, karg, context=None):
        return dict.fromkeys(ids, '')

    _columns = {
        'id': fields.integer('ID'),
        'login_date': fields.datetime('Latest connection', select=1, copy=False),
        'partner_id': fields.many2one('res.partner', required=True,
            string='Related Partner', ondelete='restrict',
            help='Partner-related data of the user', auto_join=True),
        'login': fields.char('Login', size=64, required=True,
            help="Used to log into the system"),
        'password': fields.char('Password', size=64, invisible=True, copy=False,
            help="Keep empty if you don't want the user to be able to connect on the system."),
        'new_password': fields.function(_get_password, type='char', size=64,
            fnct_inv=_set_new_password, string='Set Password',
            help="Specify a value only when creating a user or if you're "\
                 "changing the user's password, otherwise leave empty. After "\
                 "a change of password, the user has to login again."),
        'signature': fields.html('Signature'),
        'active': fields.boolean('Active'),
        'action_id': fields.many2one('ir.actions.actions', 'Home Action', help="If specified, this action will be opened at log on for this user, in addition to the standard menu."),
        'groups_id': fields.many2many('res.groups', 'res_groups_users_rel', 'uid', 'gid', 'Groups'),
        # Special behavior for this field: res.company.search() will only return the companies
        # available to the current user (should be the user's companies?), when the user_preference
        # context is set.
        'company_id': fields.many2one('res.company', 'Company', required=True,
            help='The company this user is currently working for.', context={'user_preference': True}),
        'company_ids':fields.many2many('res.company','res_company_users_rel','user_id','cid','Companies'),
    }

    # overridden inherited fields to bypass access rights, in case you have
    # access to the user but not its corresponding partner
    name = openerp.fields.Char(related='partner_id.name', inherited=True)
    email = openerp.fields.Char(related='partner_id.email', inherited=True)

    def on_change_login(self, cr, uid, ids, login, context=None):
        if login and tools.single_email_re.match(login):
            return {'value': {'email': login}}
        return {}

    def onchange_state(self, cr, uid, ids, state_id, context=None):
        partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
        return self.pool.get('res.partner').onchange_state(cr, uid, partner_ids, state_id, context=context)

    def onchange_type(self, cr, uid, ids, is_company, context=None):
        """ Wrapper on the user.partner onchange_type, because some calls to the
            partner form view applied to the user may trigger the
            partner.onchange_type method, but applied to the user object.
        """
        partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
        return self.pool['res.partner'].onchange_type(cr, uid, partner_ids, is_company, context=context)

    def onchange_address(self, cr, uid, ids, use_parent_address, parent_id, context=None):
        """ Wrapper on the user.partner onchange_address, because some calls to the
            partner form view applied to the user may trigger the
            partner.onchange_type method, but applied to the user object.
        """
        partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
        return self.pool['res.partner'].onchange_address(cr, uid, partner_ids, use_parent_address, parent_id, context=context)

    def _check_company(self, cr, uid, ids, context=None):
        return all(((this.company_id in this.company_ids) or not this.company_ids) for this in self.browse(cr, uid, ids, context))

    _constraints = [
        (_check_company, 'The chosen company is not in the allowed companies for this user', ['company_id', 'company_ids']),
    ]

    _sql_constraints = [
        ('login_key', 'UNIQUE (login)',  'You can not have two users with the same login !')
    ]

    def _get_company(self,cr, uid, context=None, uid2=False):
        if not uid2:
            uid2 = uid
        # Use read() to compute default company, and pass load=_classic_write to
        # avoid useless name_get() calls. This will avoid prefetching fields
        # while computing default values for new db columns, as the
        # db backend may not be fully initialized yet.
        user_data = self.pool['res.users'].read(cr, uid, uid2, ['company_id'],
                                                context=context, load='_classic_write')
        comp_id = user_data['company_id']
        return comp_id or False

    def _get_companies(self, cr, uid, context=None):
        c = self._get_company(cr, uid, context)
        if c:
            return [c]
        return False

    def _get_group(self,cr, uid, context=None):
        dataobj = self.pool.get('ir.model.data')
        result = []
        try:
            dummy,group_id = dataobj.get_object_reference(cr, SUPERUSER_ID, 'base', 'group_user')
            result.append(group_id)
            dummy,group_id = dataobj.get_object_reference(cr, SUPERUSER_ID, 'base', 'group_partner_manager')
            result.append(group_id)
        except ValueError:
            # If these groups does not exists anymore
            pass
        return result

    def _get_default_image(self, cr, uid, context=None):
        return self.pool['res.partner']._get_default_image(cr, uid, False, colorize=True, context=context)

    _defaults = {
        'password': '',
        'active': True,
        'customer': False,
        'company_id': _get_company,
        'company_ids': _get_companies,
        'groups_id': _get_group,
        'image': _get_default_image,
    }

    # User can write on a few of his own fields (but not his groups for example)
    SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz']
    # User can read a few of his own fields
    SELF_READABLE_FIELDS = ['signature', 'company_id', 'login', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz', 'tz_offset', 'groups_id', 'partner_id', '__last_update']

    def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
        def override_password(o):
            if ('id' not in o or o['id'] != uid):
                for f in USER_PRIVATE_FIELDS:
                    if f in o:
                        o[f] = '********'
            return o

        if fields and (ids == [uid] or ids == uid):
            for key in fields:
                if not (key in self.SELF_READABLE_FIELDS or key.startswith('context_')):
                    break
            else:
                # safe fields only, so we read as super-user to bypass access rights
                uid = SUPERUSER_ID

        result = super(res_users, self).read(cr, uid, ids, fields=fields, context=context, load=load)
        canwrite = self.pool['ir.model.access'].check(cr, uid, 'res.users', 'write', False)
        if not canwrite:
            if isinstance(ids, (int, long)):
                result = override_password(result)
            else:
                result = map(override_password, result)

        return result

    def create(self, cr, uid, vals, context=None):
        user_id = super(res_users, self).create(cr, uid, vals, context=context)
        user = self.browse(cr, uid, user_id, context=context)
        if user.partner_id.company_id: 
            user.partner_id.write({'company_id': user.company_id.id})
        return user_id

    def write(self, cr, uid, ids, values, context=None):
        if not hasattr(ids, '__iter__'):
            ids = [ids]
        if ids == [uid]:
            for key in values.keys():
                if not (key in self.SELF_WRITEABLE_FIELDS or key.startswith('context_')):
                    break
            else:
                if 'company_id' in values:
                    user = self.browse(cr, SUPERUSER_ID, uid, context=context)
                    if not (values['company_id'] in user.company_ids.ids):
                        del values['company_id']
                uid = 1 # safe fields only, so we write as super-user to bypass access rights

        res = super(res_users, self).write(cr, uid, ids, values, context=context)
        if 'company_id' in values:
            for user in self.browse(cr, uid, ids, context=context):
                # if partner is global we keep it that way
                if user.partner_id.company_id and user.partner_id.company_id.id != values['company_id']: 
                    user.partner_id.write({'company_id': user.company_id.id})
        # clear caches linked to the users
        self.pool['ir.model.access'].call_cache_clearing_methods(cr)
        clear = partial(self.pool['ir.rule'].clear_cache, cr)
        map(clear, ids)
        db = cr.dbname
        if db in self._uid_cache:
            for id in ids:
                if id in self._uid_cache[db]:
                    del self._uid_cache[db][id]
        self._context_get.clear_cache(self)
        self.has_group.clear_cache(self)
        return res

    def unlink(self, cr, uid, ids, context=None):
        if 1 in ids:
            raise UserError(_('You can not remove the admin user as it is used internally for resources created by Odoo (updates, module installation, ...)'))
        db = cr.dbname
        if db in self._uid_cache:
            for id in ids:
                if id in self._uid_cache[db]:
                    del self._uid_cache[db][id]
        return super(res_users, self).unlink(cr, uid, ids, context=context)

    def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
        if not args:
            args=[]
        if not context:
            context={}
        ids = []
        if name and operator in ['=', 'ilike']:
            ids = self.search(cr, user, [('login','=',name)]+ args, limit=limit, context=context)
        if not ids:
            ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
        return self.name_get(cr, user, ids, context=context)

    def copy(self, cr, uid, id, default=None, context=None):
        user2copy = self.read(cr, uid, [id], ['login','name'])[0]
        default = dict(default or {})
        if ('name' not in default) and ('partner_id' not in default):
            default['name'] = _("%s (copy)") % user2copy['name']
        if 'login' not in default:
            default['login'] = _("%s (copy)") % user2copy['login']
        return super(res_users, self).copy(cr, uid, id, default, context)

    @tools.ormcache(skiparg=2)
    def _context_get(self, cr, uid):
        user = self.browse(cr, SUPERUSER_ID, uid)
        result = {}
        for k in self._fields:
            if k.startswith('context_'):
                context_key = k[8:]
            elif k in ['lang', 'tz']:
                context_key = k
            else:
                context_key = False
            if context_key:
                res = getattr(user, k) or False
                if isinstance(res, models.BaseModel):
                    res = res.id
                result[context_key] = res or False
        return result

    def context_get(self, cr, uid, context=None):
        return self._context_get(cr, uid)

    def action_get(self, cr, uid, context=None):
        dataobj = self.pool['ir.model.data']
        data_id = dataobj._get_id(cr, SUPERUSER_ID, 'base', 'action_res_users_my')
        return dataobj.browse(cr, uid, data_id, context=context).res_id

    def check_super(self, passwd):
        if passwd == tools.config['admin_passwd']:
            return True
        else:
            raise openerp.exceptions.AccessDenied()

    def check_credentials(self, cr, uid, password):
        """ Override this method to plug additional authentication methods"""
        res = self.search(cr, SUPERUSER_ID, [('id','=',uid),('password','=',password)])
        if not res:
            raise openerp.exceptions.AccessDenied()

    def _login(self, db, login, password):
        if not password:
            return False
        user_id = False
        cr = self.pool.cursor()
        try:
            # autocommit: our single update request will be performed atomically.
            # (In this way, there is no opportunity to have two transactions
            # interleaving their cr.execute()..cr.commit() calls and have one
            # of them rolled back due to a concurrent access.)
            cr.autocommit(True)
            # check if user exists
            res = self.search(cr, SUPERUSER_ID, [('login','=',login)])
            if res:
                user_id = res[0]
                # check credentials
                self.check_credentials(cr, user_id, password)
                # We effectively unconditionally write the res_users line.
                # Even w/ autocommit there's a chance the user row will be locked,
                # in which case we can't delay the login just for the purpose of
                # update the last login date - hence we use FOR UPDATE NOWAIT to
                # try to get the lock - fail-fast
                # Failing to acquire the lock on the res_users row probably means
                # another request is holding it. No big deal, we don't want to
                # prevent/delay login in that case. It will also have been logged
                # as a SQL error, if anyone cares.
                try:
                    # NO KEY introduced in PostgreSQL 9.3 http://www.postgresql.org/docs/9.3/static/release-9-3.html#AEN115299
                    update_clause = 'NO KEY UPDATE' if cr._cnx.server_version >= 90300 else 'UPDATE'
                    cr.execute("SELECT id FROM res_users WHERE id=%%s FOR %s NOWAIT" % update_clause, (user_id,), log_exceptions=False)
                    cr.execute("UPDATE res_users SET login_date = now() AT TIME ZONE 'UTC' WHERE id=%s", (user_id,))
                    self.invalidate_cache(cr, user_id, ['login_date'], [user_id])
                except Exception:
                    _logger.debug("Failed to update last_login for db:%s login:%s", db, login, exc_info=True)
        except openerp.exceptions.AccessDenied:
            _logger.info("Login failed for db:%s login:%s", db, login)
            user_id = False
        finally:
            cr.close()

        return user_id

    def authenticate(self, db, login, password, user_agent_env):
        """Verifies and returns the user ID corresponding to the given
          ``login`` and ``password`` combination, or False if there was
          no matching user.

           :param str db: the database on which user is trying to authenticate
           :param str login: username
           :param str password: user password
           :param dict user_agent_env: environment dictionary describing any
               relevant environment attributes
        """
        uid = self._login(db, login, password)
        if uid == openerp.SUPERUSER_ID:
            # Successfully logged in as admin!
            # Attempt to guess the web base url...
            if user_agent_env and user_agent_env.get('base_location'):
                cr = self.pool.cursor()
                try:
                    base = user_agent_env['base_location']
                    ICP = self.pool['ir.config_parameter']
                    if not ICP.get_param(cr, uid, 'web.base.url.freeze'):
                        ICP.set_param(cr, uid, 'web.base.url', base)
                    cr.commit()
                except Exception:
                    _logger.exception("Failed to update web.base.url configuration parameter")
                finally:
                    cr.close()
        return uid

    def check(self, db, uid, passwd):
        """Verifies that the given (uid, password) is authorized for the database ``db`` and
           raise an exception if it is not."""
        if not passwd:
            # empty passwords disallowed for obvious security reasons
            raise openerp.exceptions.AccessDenied()
        if self._uid_cache.get(db, {}).get(uid) == passwd:
            return
        cr = self.pool.cursor()
        try:
            self.check_credentials(cr, uid, passwd)
            if self._uid_cache.has_key(db):
                self._uid_cache[db][uid] = passwd
            else:
                self._uid_cache[db] = {uid:passwd}
        finally:
            cr.close()

    def change_password(self, cr, uid, old_passwd, new_passwd, context=None):
        """Change current user password. Old password must be provided explicitly
        to prevent hijacking an existing user session, or for cases where the cleartext
        password is not used to authenticate requests.

        :return: True
        :raise: openerp.exceptions.AccessDenied when old password is wrong
        :raise: except_osv when new password is not set or empty
        """
        self.check(cr.dbname, uid, old_passwd)
        if new_passwd:
            return self.write(cr, uid, uid, {'password': new_passwd})
        raise UserError(_("Setting empty passwords is not allowed for security reasons!"))

    def preference_save(self, cr, uid, ids, context=None):
        return {
            'type': 'ir.actions.client',
            'tag': 'reload_context',
        }

    def preference_change_password(self, cr, uid, ids, context=None):
        return {
            'type': 'ir.actions.client',
            'tag': 'change_password',
            'target': 'new',
        }

    @tools.ormcache(skiparg=2)
    def has_group(self, cr, uid, group_ext_id):
        """Checks whether user belongs to given group.

        :param str group_ext_id: external ID (XML ID) of the group.
           Must be provided in fully-qualified form (``module.ext_id``), as there
           is no implicit module to use..
        :return: True if the current user is a member of the group with the
           given external ID (XML ID), else False.
        """
        assert group_ext_id and '.' in group_ext_id, "External ID must be fully qualified"
        module, ext_id = group_ext_id.split('.')
        cr.execute("""SELECT 1 FROM res_groups_users_rel WHERE uid=%s AND gid IN
                        (SELECT res_id FROM ir_model_data WHERE module=%s AND name=%s)""",
                   (uid, module, ext_id))
        return bool(cr.fetchone())

    def get_company_currency_id(self, cr, uid, context=None):
        return self.browse(cr, uid, uid, context=context).company_id.currency_id.id
예제 #3
0
파일: order.py 프로젝트: osiell/OCB
class sale_order_option(osv.osv):
    _name = "sale.order.option"
    _description = "Sale Options"
    _columns = {
        'order_id':
        fields.many2one('sale.order',
                        'Sale Order Reference',
                        ondelete='cascade',
                        select=True),
        'line_id':
        fields.many2one('sale.order.line', on_delete="set null"),
        'name':
        fields.text('Description', required=True),
        'product_id':
        fields.many2one('product.product',
                        'Product',
                        domain=[('sale_ok', '=', True)]),
        'website_description':
        fields.html('Line Description'),
        'price_unit':
        fields.float('Unit Price',
                     required=True,
                     digits_compute=dp.get_precision('Product Price')),
        'discount':
        fields.float('Discount (%)',
                     digits_compute=dp.get_precision('Discount')),
        'uom_id':
        fields.many2one('product.uom', 'Unit of Measure ', required=True),
        'quantity':
        fields.float('Quantity',
                     required=True,
                     digits_compute=dp.get_precision('Product UoS')),
    }

    _defaults = {
        'quantity': 1,
    }

    # TODO master: to remove, replaced by onchange of the new api
    def on_change_product_id(self,
                             cr,
                             uid,
                             ids,
                             product,
                             uom_id=None,
                             context=None):
        vals, domain = {}, []
        if not product:
            return vals
        product_obj = self.pool.get('product.product').browse(cr,
                                                              uid,
                                                              product,
                                                              context=context)
        name = product_obj.name
        if product_obj.description_sale:
            name += '\n' + product_obj.description_sale
        vals.update({
            'price_unit':
            product_obj.list_price,
            'website_description':
            product_obj and
            (product_obj.quote_description or product_obj.website_description),
            'name':
            name,
            'uom_id':
            uom_id or product_obj.uom_id.id,
        })
        uom_obj = self.pool.get('product.uom')
        if vals['uom_id'] != product_obj.uom_id.id:
            selected_uom = uom_obj.browse(cr,
                                          uid,
                                          vals['uom_id'],
                                          context=context)
            new_price = uom_obj._compute_price(cr, uid, product_obj.uom_id.id,
                                               vals['price_unit'],
                                               vals['uom_id'])
            vals['price_unit'] = new_price
        if not uom_id:
            domain = {
                'uom_id':
                [('category_id', '=', product_obj.uom_id.category_id.id)]
            }
        return {'value': vals, 'domain': domain}

    # TODO master: to remove, replaced by onchange of the new api
    def product_uom_change(self, cr, uid, ids, product, uom_id, context=None):
        context = context or {}
        if not uom_id:
            return {'value': {'price_unit': 0.0, 'uom_id': False}}
        return self.on_change_product_id(cr,
                                         uid,
                                         ids,
                                         product,
                                         uom_id=uom_id,
                                         context=context)

    @api.onchange('product_id', 'uom_id')
    def _onchange_product_id(self):
        if not self.product_id:
            return
        product = self.product_id.with_context(
            lang=self.order_id.partner_id.lang)
        self.price_unit = product.list_price
        self.website_description = product.quote_description or product.website_description
        self.name = product.name
        if product.description_sale:
            self.name += '\n' + product.description_sale
        self.uom_id = self.uom_id or product.uom_id
        pricelist = self.order_id.pricelist_id
        if pricelist and product:
            partner_id = self.order_id.partner_id.id
            self.price_unit = pricelist.with_context(
                uom=self.uom_id.id).price_get(product.id, self.quantity,
                                              partner_id)[pricelist.id]
        domain = {
            'uom_id':
            [('category_id', '=', self.product_id.uom_id.category_id.id)]
        }
        return {'domain': domain}
예제 #4
0
파일: order.py 프로젝트: osiell/OCB
class sale_order(osv.osv):
    _inherit = 'sale.order'

    def _get_total(self, cr, uid, ids, name, arg, context=None):
        res = {}
        for order in self.browse(cr, uid, ids, context=context):
            total = 0.0
            for line in order.order_line:
                total += line.price_subtotal + line.price_unit * (
                    (line.discount or 0.0) / 100.0) * line.product_uom_qty
            res[order.id] = total
        return res

    _columns = {
        'access_token':
        fields.char('Security Token', required=True, copy=False),
        'template_id':
        fields.many2one('sale.quote.template',
                        'Quotation Template',
                        readonly=True,
                        states={
                            'draft': [('readonly', False)],
                            'sent': [('readonly', False)]
                        }),
        'website_description':
        fields.html('Description', translate=True),
        'options':
        fields.one2many('sale.order.option',
                        'order_id',
                        'Optional Products Lines',
                        readonly=True,
                        states={
                            'draft': [('readonly', False)],
                            'sent': [('readonly', False)]
                        },
                        copy=True),
        'amount_undiscounted':
        fields.function(_get_total,
                        string='Amount Before Discount',
                        type="float",
                        digits=0),
        'quote_viewed':
        fields.boolean('Quotation Viewed'),
        'require_payment':
        fields.selection(
            [(0, 'Not mandatory on website quote validation'),
             (1, 'Immediate after website order validation')],
            'Payment',
            help=
            "Require immediate payment by the customer when validating the order from the website quote"
        ),
    }

    def _get_template_id(self, cr, uid, context=None):
        try:
            template_id = self.pool.get('ir.model.data').get_object_reference(
                cr, uid, 'website_quote', 'website_quote_template_default')[1]
        except ValueError:
            template_id = False
        return template_id

    _defaults = {
        'access_token': lambda self, cr, uid, ctx={}: str(uuid.uuid4()),
        'template_id': _get_template_id,
    }

    def open_quotation(self, cr, uid, quote_id, context=None):
        quote = self.browse(cr, uid, quote_id[0], context=context)
        self.write(cr,
                   uid,
                   quote_id[0], {'quote_viewed': True},
                   context=context)
        return {
            'type': 'ir.actions.act_url',
            'target': 'self',
            'url': '/quote/%s/%s' % (quote.id, quote.access_token)
        }

    def onchange_template_id(self,
                             cr,
                             uid,
                             ids,
                             template_id,
                             partner=False,
                             fiscal_position_id=False,
                             pricelist_id=False,
                             context=None):
        if not template_id:
            return {}

        if partner:
            context = dict(context or {})
            context['lang'] = self.pool['res.partner'].browse(
                cr, uid, partner, context).lang

        pricelist_obj = self.pool['product.pricelist']

        lines = [(5, )]
        quote_template = self.pool.get('sale.quote.template').browse(
            cr, uid, template_id, context=context)
        for line in quote_template.quote_line:
            date = time.strftime('%Y-%m-%d')
            res = self.pool.get('sale.order.line').product_id_change(
                cr, uid, False, False, line.product_id.id,
                line.product_uom_qty, line.product_uom_id.id,
                line.product_uom_qty, line.product_uom_id.id, line.name,
                partner, False, True, date, False, fiscal_position_id, True,
                context)
            data = res.get('value', {})
            if pricelist_id:
                pricelist = self.pool['product.pricelist'].browse(
                    cr, uid, [pricelist_id], context=context)[0]
                data.update(self.pool['sale.order.line']._get_purchase_price(
                    cr,
                    uid,
                    pricelist,
                    line.product_id,
                    line.product_uom_id,
                    date,
                    context=context))
                uom_context = context.copy()
                uom_context['uom'] = line.product_uom_id.id
                price = pricelist_obj.price_get(
                    cr,
                    uid, [pricelist_id],
                    line.product_id.id,
                    1,
                    context=uom_context)[pricelist_id]
            else:
                price = line.price_unit

            if 'tax_id' in data:
                data['tax_id'] = [(6, 0, data['tax_id'])]
            else:
                fpos = (fiscal_position_id
                        and self.pool['account.fiscal.position'].browse(
                            cr, uid, fiscal_position_id)) or False
                taxes = fpos.map_tax(
                    line.product_id.product_tmpl_id.taxes_id
                ).ids if fpos else line.product_id.product_tmpl_id.taxes_id.ids
                data['tax_id'] = [(6, 0, taxes)]
            data.update({
                'name':
                line.name,
                'price_unit':
                price,
                'discount':
                line.discount,
                'product_uom_qty':
                line.product_uom_qty,
                'product_id':
                line.product_id.id,
                'product_uom':
                line.product_uom_id.id,
                'website_description':
                line.website_description,
                'state':
                'draft',
                'customer_lead':
                self._get_customer_lead(cr, uid,
                                        line.product_id.product_tmpl_id),
            })
            lines.append((0, 0, data))
        options = []
        for option in quote_template.options:
            if pricelist_id:
                uom_context = context.copy()
                uom_context['uom'] = option.uom_id.id
                price = pricelist_obj.price_get(
                    cr,
                    uid, [pricelist_id],
                    option.product_id.id,
                    1,
                    context=uom_context)[pricelist_id]
            else:
                price = option.price_unit
            options.append((0, 0, {
                'product_id': option.product_id.id,
                'name': option.name,
                'quantity': option.quantity,
                'uom_id': option.uom_id.id,
                'price_unit': price,
                'discount': option.discount,
                'website_description': option.website_description,
            }))
        date = False
        if quote_template.number_of_days > 0:
            date = (datetime.datetime.now() + datetime.timedelta(
                quote_template.number_of_days)).strftime("%Y-%m-%d")
        data = {
            'order_line': lines,
            'website_description': quote_template.website_description,
            'options': options,
            'validity_date': date,
            'require_payment': quote_template.require_payment
        }
        if quote_template.note:
            data['note'] = quote_template.note
        return {'value': data}

    def recommended_products(self, cr, uid, ids, context=None):
        order_line = self.browse(cr, uid, ids[0], context=context).order_line
        product_pool = self.pool.get('product.product')
        products = []
        for line in order_line:
            products += line.product_id.product_tmpl_id.recommended_products(
                context=context)
        return products

    def get_access_action(self, cr, uid, ids, context=None):
        """ Override method that generated the link to access the document. Instead
        of the classic form view, redirect to the online quote if exists. """
        quote = self.browse(cr, uid, ids[0], context=context)
        if not quote.template_id:
            return super(sale_order, self).get_access_action(cr,
                                                             uid,
                                                             ids,
                                                             context=context)
        return {
            'type': 'ir.actions.act_url',
            'url': '/quote/%s' % quote.id,
            'target': 'self',
            'res_id': quote.id,
        }

    def action_quotation_send(self, cr, uid, ids, context=None):
        action = super(sale_order, self).action_quotation_send(cr,
                                                               uid,
                                                               ids,
                                                               context=context)
        ir_model_data = self.pool.get('ir.model.data')
        quote_template_id = self.read(cr,
                                      uid,
                                      ids, ['template_id'],
                                      context=context)[0]['template_id']
        if quote_template_id:
            try:
                template_id = ir_model_data.get_object_reference(
                    cr, uid, 'website_quote', 'email_template_edi_sale')[1]
            except ValueError:
                pass
            else:
                action['context'].update({
                    'default_template_id': template_id,
                    'default_use_template': True
                })

        return action

    def _confirm_online_quote(self, cr, uid, order_id, tx, context=None):
        """ Payment callback: validate the order and write tx details in chatter """
        order = self.browse(cr, uid, order_id, context=context)

        # create draft invoice if transaction is ok
        if tx and tx.state == 'done':
            if order.state in ['draft', 'sent']:
                self.action_confirm(cr,
                                    SUPERUSER_ID,
                                    order.id,
                                    context=context)
            message = _('Order payed by %s. Transaction: %s. Amount: %s.') % (
                tx.partner_id.name, tx.acquirer_reference, tx.amount)
            self.message_post(cr, uid, order_id, body=message, context=context)
            return True
        return False

    def create(self, cr, uid, values, context=None):
        if not values.get('template_id'):
            defaults = self.default_get(cr,
                                        uid, ['template_id'],
                                        context=context)
            template_values = self.onchange_template_id(
                cr,
                uid, [],
                defaults.get('template_id'),
                partner=values.get('partner_id'),
                fiscal_position_id=values.get('fiscal_position'),
                context=context).get('value', {})
            values = dict(template_values, **values)
        return super(sale_order, self).create(cr, uid, values, context=context)

    def _get_payment_type(self, cr, uid, ids, context=None):
        return 'form'

    def _set_default_value_on_column(self, cr, column_name, context=None):
        if column_name != 'access_token':
            super(sale_order,
                  self)._set_default_value_on_column(cr,
                                                     column_name,
                                                     context=context)
        else:
            query = """UPDATE %(table_name)s
                          SET %(column_name)s = md5(random()::text || clock_timestamp()::text)::uuid
                        WHERE %(column_name)s IS NULL
                    """ % {
                'table_name': self._table,
                'column_name': column_name
            }
            cr.execute(query)
예제 #5
0
class sale_order(osv.osv):
    _inherit = 'sale.order'

    def _get_total(self, cr, uid, ids, name, arg, context=None):
        res = {}
        for order in self.browse(cr, uid, ids, context=context):
            total = 0.0
            for line in order.order_line:
                total += (line.product_uom_qty * line.price_unit)
            res[order.id] = total
        return res

    _columns = {
        'access_token':
        fields.char('Security Token', required=True, copy=False),
        'template_id':
        fields.many2one('sale.quote.template',
                        'Quotation Template',
                        readonly=True,
                        states={
                            'draft': [('readonly', False)],
                            'sent': [('readonly', False)]
                        }),
        'website_description':
        fields.html('Description'),
        'options':
        fields.one2many('sale.order.option',
                        'order_id',
                        'Optional Products Lines',
                        readonly=True,
                        states={
                            'draft': [('readonly', False)],
                            'sent': [('readonly', False)]
                        },
                        copy=True),
        'amount_undiscounted':
        fields.function(_get_total,
                        string='Amount Before Discount',
                        type="float",
                        digits=0),
        'quote_viewed':
        fields.boolean('Quotation Viewed'),
        'require_payment':
        fields.boolean(
            'Immediate Payment',
            help=
            "Require immediate payment by the customer when validating the order from the website quote"
        ),
    }

    def _get_template_id(self, cr, uid, context=None):
        try:
            template_id = self.pool.get('ir.model.data').get_object_reference(
                cr, uid, 'website_quote', 'website_quote_template_default')[1]
        except ValueError:
            template_id = False
        return template_id

    _defaults = {
        'access_token': lambda self, cr, uid, ctx={}: str(uuid.uuid4()),
        'template_id': _get_template_id,
    }

    def open_quotation(self, cr, uid, quote_id, context=None):
        quote = self.browse(cr, uid, quote_id[0], context=context)
        self.write(cr,
                   uid,
                   quote_id[0], {'quote_viewed': True},
                   context=context)
        return {
            'type': 'ir.actions.act_url',
            'target': 'self',
            'url': '/quote/%s/%s' % (quote.id, quote.access_token)
        }

    def onchange_template_id(self,
                             cr,
                             uid,
                             ids,
                             template_id,
                             partner=False,
                             fiscal_position_id=False,
                             pricelist_id=False,
                             context=None):
        if not template_id:
            return True

        if partner:
            context = dict(context or {})
            context['lang'] = self.pool['res.partner'].browse(
                cr, uid, partner, context).lang

        pricelist_obj = self.pool['product.pricelist']

        lines = [(5, )]
        quote_template = self.pool.get('sale.quote.template').browse(
            cr, uid, template_id, context=context)
        for line in quote_template.quote_line:
            res = self.pool.get('sale.order.line').product_id_change(
                cr, uid, False, False, line.product_id.id,
                line.product_uom_qty, line.product_uom_id.id,
                line.product_uom_qty,
                line.product_uom_id.id, line.name, partner, False, True,
                time.strftime('%Y-%m-%d'), False, fiscal_position_id, True,
                context)
            data = res.get('value', {})
            if pricelist_id:
                price = pricelist_obj.price_get(cr,
                                                uid, [pricelist_id],
                                                line.product_id.id,
                                                1,
                                                context=context)[pricelist_id]
            else:
                price = line.price_unit

            if 'tax_id' in data:
                data['tax_id'] = [(6, 0, data['tax_id'])]
            data.update({
                'name': line.name,
                'price_unit': price,
                'discount': line.discount,
                'product_uom_qty': line.product_uom_qty,
                'product_id': line.product_id.id,
                'product_uom': line.product_uom_id.id,
                'website_description': line.website_description,
                'state': 'draft',
            })
            lines.append((0, 0, data))
        options = []
        for option in quote_template.options:
            if pricelist_id:
                price = pricelist_obj.price_get(cr,
                                                uid, [pricelist_id],
                                                option.product_id.id,
                                                1,
                                                context=context)[pricelist_id]
            else:
                price = option.price_unit
            options.append((0, 0, {
                'product_id': option.product_id.id,
                'name': option.name,
                'quantity': option.quantity,
                'uom_id': option.uom_id.id,
                'price_unit': price,
                'discount': option.discount,
                'website_description': option.website_description,
            }))
        date = False
        if quote_template.number_of_days > 0:
            date = (datetime.datetime.now() + datetime.timedelta(
                quote_template.number_of_days)).strftime("%Y-%m-%d")
        data = {
            'order_line': lines,
            'website_description': quote_template.website_description,
            'note': quote_template.note,
            'options': options,
            'validity_date': date,
            'require_payment': quote_template.require_payment
        }
        return {'value': data}

    def recommended_products(self, cr, uid, ids, context=None):
        order_line = self.browse(cr, uid, ids[0], context=context).order_line
        product_pool = self.pool.get('product.product')
        products = []
        for line in order_line:
            products += line.product_id.product_tmpl_id.recommended_products(
                context=context)
        return products

    def get_access_action(self, cr, uid, id, context=None):
        """ Override method that generated the link to access the document. Instead
        of the classic form view, redirect to the online quote if exists. """
        quote = self.browse(cr, uid, id, context=context)
        if not quote.template_id:
            return super(sale_order, self).get_access_action(cr,
                                                             uid,
                                                             id,
                                                             context=context)
        return {
            'type': 'ir.actions.act_url',
            'url': '/quote/%s' % id,
            'target': 'self',
            'res_id': id,
        }

    def action_quotation_send(self, cr, uid, ids, context=None):
        action = super(sale_order, self).action_quotation_send(cr,
                                                               uid,
                                                               ids,
                                                               context=context)
        ir_model_data = self.pool.get('ir.model.data')
        quote_template_id = self.read(cr,
                                      uid,
                                      ids, ['template_id'],
                                      context=context)[0]['template_id']
        if quote_template_id:
            try:
                template_id = ir_model_data.get_object_reference(
                    cr, uid, 'website_quote', 'email_template_edi_sale')[1]
            except ValueError:
                pass
            else:
                action['context'].update({
                    'default_template_id': template_id,
                    'default_use_template': True
                })

        return action
예제 #6
0
class crops(osv.Model):
    _name = 'crops'
    _description = 'Crops'
    _columns = {
        'irrigation_line':
        fields.one2many('irrigation.details', 'irrigation'),
        'landpreparation_line':
        fields.one2many('landpreparation.details', 'landpreparation'),
        'fertilizer_line':
        fields.one2many('fertilizer.details', 'fertilizer'),
        'sowing_line':
        fields.one2many('sowing.details', 'sowing'),
        'sampling_line':
        fields.one2many('sampling.details', 'sampling'),
        'survey_line':
        fields.one2many('survey.details', 'survey'),
        'harvesting_line':
        fields.one2many('harvesting.details', 'harvesting'),
        'scouting_line':
        fields.one2many('scouting.details', 'scout'),
        'pest_line':
        fields.one2many('pest.issue', 'pest'),
        'weeding_line':
        fields.one2many('weeding.controle', 'weeding'),
        'disease_line':
        fields.one2many('disease.issue', 'disease'),
        'weed_line':
        fields.one2many('weed.issue', 'weed'),
        'insect_line':
        fields.one2many('insect.issue', 'insect'),
        'treat_line':
        fields.one2many('treat.issue', 'treat'),
        'crop_name':
        fields.char('Cultivation Name', size=64, required=True),
        'land_name':
        fields.many2one('plot', 'Plot Name'),
        'type':
        fields.char('Crop Type', size=64),
        'fert_used':
        fields.char('Fertilizers Used', size=64),
        'sowing_date':
        fields.datetime('Planting Date'),
        'harvesting_date':
        fields.datetime('Harvesting Date'),
        'crop_variety':
        fields.char('Crop Variety', size=64),
        'year':
        fields.integer('Year of Cultivation', size=64),
        'current_crop':
        fields.char('Current Crop', size=64),
        'crop_yield':
        fields.integer('Yield', size=64),
        'des':
        fields.html('Description'),
        'current_market_price':
        fields.integer('Market Price'),
        'actual_productivity':
        fields.integer("Actual Productivity", size=64),
        'estimated_productivity':
        fields.integer('Estimated Productivity', size=64),
        'state':
        fields.selection([('initial', 'Initial'), ('surveyed', 'Surveyed'),
                          ('landprepared', 'Land Preparation'),
                          ('planted', 'Planting'),
                          ('intercropping', 'Intercropping'),
                          ('harvest', 'Harvesting'), ('ratoon', 'Ratooning')],
                         'Status',
                         readonly=False,
                         help='The status of the current cultivation',
                         copy=False),
    }
예제 #7
0
class MassMailing(osv.Model):
    """ MassMailing models a wave of emails for a mass mailign campaign.
    A mass mailing is an occurence of sending emails. """

    _name = 'mail.mass_mailing'
    _description = 'Mass Mailing'
    # number of periods for tracking mail_mail statistics
    _period_number = 6
    _order = 'sent_date DESC'

    def __get_bar_values(self,
                         cr,
                         uid,
                         obj,
                         domain,
                         read_fields,
                         value_field,
                         groupby_field,
                         date_begin,
                         context=None):
        """ Generic method to generate data for bar chart values using SparklineBarWidget.
            This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).

            :param obj: the target model (i.e. crm_lead)
            :param domain: the domain applied to the read_group
            :param list read_fields: the list of fields to read in the read_group
            :param str value_field: the field used to compute the value of the bar slice
            :param str groupby_field: the fields used to group

            :return list section_result: a list of dicts: [
                                                {   'value': (int) bar_column_value,
                                                    'tootip': (str) bar_column_tooltip,
                                                }
                                            ]
        """
        date_begin = date_begin.date()
        section_result = [{
            'value':
            0,
            'tooltip':
            (date_begin +
             relativedelta.relativedelta(days=i)).strftime('%d %B %Y'),
        } for i in range(0, self._period_number)]
        group_obj = obj.read_group(cr,
                                   uid,
                                   domain,
                                   read_fields,
                                   groupby_field,
                                   context=context)
        field_col_info = obj._all_columns.get(groupby_field.split(':')[0])
        pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field_col_info.column._type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
        for group in group_obj:
            group_begin_date = datetime.strptime(group['__domain'][0][2],
                                                 pattern).date()
            timedelta = relativedelta.relativedelta(group_begin_date,
                                                    date_begin)
            section_result[timedelta.days] = {
                'value': group.get(value_field, 0),
                'tooltip': group.get(groupby_field)
            }
        return section_result

    def _get_daily_statistics(self,
                              cr,
                              uid,
                              ids,
                              field_name,
                              arg,
                              context=None):
        """ Get the daily statistics of the mass mailing. This is done by a grouping
        on opened and replied fields. Using custom format in context, we obtain
        results for the next 6 days following the mass mailing date. """
        obj = self.pool['mail.mail.statistics']
        res = {}
        for mailing in self.browse(cr, uid, ids, context=context):
            res[mailing.id] = {}
            date = mailing.sent_date if mailing.sent_date else mailing.create_date
            date_begin = datetime.strptime(
                date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
            date_end = date_begin + relativedelta.relativedelta(
                days=self._period_number - 1)
            date_begin_str = date_begin.strftime(
                tools.DEFAULT_SERVER_DATETIME_FORMAT)
            date_end_str = date_end.strftime(
                tools.DEFAULT_SERVER_DATETIME_FORMAT)
            domain = [('mass_mailing_id', '=', mailing.id),
                      ('opened', '>=', date_begin_str),
                      ('opened', '<=', date_end_str)]
            res[mailing.id]['opened_daily'] = json.dumps(
                self.__get_bar_values(cr,
                                      uid,
                                      obj,
                                      domain, ['opened'],
                                      'opened_count',
                                      'opened:day',
                                      date_begin,
                                      context=context))
            domain = [('mass_mailing_id', '=', mailing.id),
                      ('replied', '>=', date_begin_str),
                      ('replied', '<=', date_end_str)]
            res[mailing.id]['replied_daily'] = json.dumps(
                self.__get_bar_values(cr,
                                      uid,
                                      obj,
                                      domain, ['replied'],
                                      'replied_count',
                                      'replied:day',
                                      date_begin,
                                      context=context))
        return res

    def _get_statistics(self, cr, uid, ids, name, arg, context=None):
        """ Compute statistics of the mass mailing campaign """
        results = {}
        cr.execute(
            """
            SELECT
                m.id as mailing_id,
                COUNT(s.id) AS total,
                COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent,
                COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null THEN 1 ELSE null END) AS scheduled,
                COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is not null THEN 1 ELSE null END) AS failed,
                COUNT(CASE WHEN s.id is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered,
                COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened,
                COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied ,
                COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced
            FROM
                mail_mail_statistics s
            RIGHT JOIN
                mail_mass_mailing m
                ON (m.id = s.mass_mailing_id)
            WHERE
                m.id IN %s
            GROUP BY
                m.id
        """, (tuple(ids), ))
        for row in cr.dictfetchall():
            results[row.pop('mailing_id')] = row
            total = row['total'] or 1
            row['delivered'] = row['sent'] - row['bounced']
            row['received_ratio'] = 100.0 * row['delivered'] / total
            row['opened_ratio'] = 100.0 * row['opened'] / total
            row['replied_ratio'] = 100.0 * row['replied'] / total
        return results

    def _get_mailing_model(self, cr, uid, context=None):
        res = []
        for model_name in self.pool:
            model = self.pool[model_name]
            if hasattr(model, '_mail_mass_mailing') and getattr(
                    model, '_mail_mass_mailing'):
                res.append((model._name, getattr(model, '_mail_mass_mailing')))
        res.append(('mail.mass_mailing.contact', _('Mailing List')))
        return res

    # indirections for inheritance
    _mailing_model = lambda self, *args, **kwargs: self._get_mailing_model(
        *args, **kwargs)

    _columns = {
        'name':
        fields.char('Subject', required=True),
        'email_from':
        fields.char('From', required=True),
        'create_date':
        fields.datetime('Creation Date'),
        'sent_date':
        fields.datetime('Sent Date', oldname='date', copy=False),
        'body_html':
        fields.html('Body'),
        'attachment_ids':
        fields.many2many('ir.attachment', 'mass_mailing_ir_attachments_rel',
                         'mass_mailing_id', 'attachment_id', 'Attachments'),
        'mass_mailing_campaign_id':
        fields.many2one(
            'mail.mass_mailing.campaign',
            'Mass Mailing Campaign',
            ondelete='set null',
        ),
        'state':
        fields.selection(
            [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')],
            string='Status',
            required=True,
            copy=False,
        ),
        'color':
        fields.related(
            'mass_mailing_campaign_id',
            'color',
            type='integer',
            string='Color Index',
        ),
        # mailing options
        'reply_to_mode':
        fields.selection(
            [('thread', 'In Document'), ('email', 'Specified Email Address')],
            string='Reply-To Mode',
            required=True,
        ),
        'reply_to':
        fields.char('Reply To', help='Preferred Reply-To Address'),
        # recipients
        'mailing_model':
        fields.selection(_mailing_model,
                         string='Recipients Model',
                         required=True),
        'mailing_domain':
        fields.char('Domain', oldname='domain'),
        'contact_list_ids':
        fields.many2many(
            'mail.mass_mailing.list',
            'mail_mass_mailing_list_rel',
            string='Mailing Lists',
        ),
        'contact_ab_pc':
        fields.integer(
            'AB Testing percentage',
            help=
            'Percentage of the contacts that will be mailed. Recipients will be taken randomly.'
        ),
        # statistics data
        'statistics_ids':
        fields.one2many(
            'mail.mail.statistics',
            'mass_mailing_id',
            'Emails Statistics',
        ),
        'total':
        fields.function(
            _get_statistics,
            string='Total',
            type='integer',
            multi='_get_statistics',
        ),
        'scheduled':
        fields.function(
            _get_statistics,
            string='Scheduled',
            type='integer',
            multi='_get_statistics',
        ),
        'failed':
        fields.function(
            _get_statistics,
            string='Failed',
            type='integer',
            multi='_get_statistics',
        ),
        'sent':
        fields.function(
            _get_statistics,
            string='Sent',
            type='integer',
            multi='_get_statistics',
        ),
        'delivered':
        fields.function(
            _get_statistics,
            string='Delivered',
            type='integer',
            multi='_get_statistics',
        ),
        'opened':
        fields.function(
            _get_statistics,
            string='Opened',
            type='integer',
            multi='_get_statistics',
        ),
        'replied':
        fields.function(
            _get_statistics,
            string='Replied',
            type='integer',
            multi='_get_statistics',
        ),
        'bounced':
        fields.function(
            _get_statistics,
            string='Bounced',
            type='integer',
            multi='_get_statistics',
        ),
        'received_ratio':
        fields.function(
            _get_statistics,
            string='Received Ratio',
            type='integer',
            multi='_get_statistics',
        ),
        'opened_ratio':
        fields.function(
            _get_statistics,
            string='Opened Ratio',
            type='integer',
            multi='_get_statistics',
        ),
        'replied_ratio':
        fields.function(
            _get_statistics,
            string='Replied Ratio',
            type='integer',
            multi='_get_statistics',
        ),
        # daily ratio
        'opened_daily':
        fields.function(
            _get_daily_statistics,
            string='Opened',
            type='char',
            multi='_get_daily_statistics',
        ),
        'replied_daily':
        fields.function(
            _get_daily_statistics,
            string='Replied',
            type='char',
            multi='_get_daily_statistics',
        )
    }

    def default_get(self, cr, uid, fields, context=None):
        res = super(MassMailing, self).default_get(cr,
                                                   uid,
                                                   fields,
                                                   context=context)
        if 'reply_to_mode' in fields and not 'reply_to_mode' in res and res.get(
                'mailing_model'):
            if res['mailing_model'] in [
                    'res.partner', 'mail.mass_mailing.contact'
            ]:
                res['reply_to_mode'] = 'email'
            else:
                res['reply_to_mode'] = 'thread'
        return res

    _defaults = {
        'state':
        'draft',
        'email_from':
        lambda self, cr, uid, ctx=None: self.pool[
            'mail.message']._get_default_from(cr, uid, context=ctx),
        'reply_to':
        lambda self, cr, uid, ctx=None: self.pool['mail.message'].
        _get_default_from(cr, uid, context=ctx),
        'mailing_model':
        'mail.mass_mailing.contact',
        'contact_ab_pc':
        100,
    }

    #------------------------------------------------------
    # Technical stuff
    #------------------------------------------------------

    def copy_data(self, cr, uid, id, default=None, context=None):
        mailing = self.browse(cr, uid, id, context=context)
        default = dict(default or {}, name=_('%s (copy)') % mailing.name)
        return super(MassMailing, self).copy_data(cr,
                                                  uid,
                                                  id,
                                                  default,
                                                  context=context)

    def read_group(self,
                   cr,
                   uid,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   context=None,
                   orderby=False,
                   lazy=True):
        """ Override read_group to always display all states. """
        if groupby and groupby[0] == "state":
            # Default result structure
            # states = self._get_state_list(cr, uid, context=context)
            states = [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')]
            read_group_all_states = [{
                '__context': {
                    'group_by': groupby[1:]
                },
                '__domain':
                domain + [('state', '=', state_value)],
                'state':
                state_value,
                'state_count':
                0,
            } for state_value, state_name in states]
            # Get standard results
            read_group_res = super(MassMailing,
                                   self).read_group(cr,
                                                    uid,
                                                    domain,
                                                    fields,
                                                    groupby,
                                                    offset=offset,
                                                    limit=limit,
                                                    context=context,
                                                    orderby=orderby)
            # Update standard results with default results
            result = []
            for state_value, state_name in states:
                res = filter(lambda x: x['state'] == state_value,
                             read_group_res)
                if not res:
                    res = filter(lambda x: x['state'] == state_value,
                                 read_group_all_states)
                res[0]['state'] = [state_value, state_name]
                result.append(res[0])
            return result
        else:
            return super(MassMailing, self).read_group(cr,
                                                       uid,
                                                       domain,
                                                       fields,
                                                       groupby,
                                                       offset=offset,
                                                       limit=limit,
                                                       context=context,
                                                       orderby=orderby)

    #------------------------------------------------------
    # Views & Actions
    #------------------------------------------------------

    def on_change_model_and_list(self,
                                 cr,
                                 uid,
                                 ids,
                                 mailing_model,
                                 list_ids,
                                 context=None):
        value = {}
        if mailing_model == 'mail.mass_mailing.contact':
            mailing_list_ids = set()
            for item in list_ids:
                if isinstance(item, (int, long)):
                    mailing_list_ids.add(item)
                elif len(item) == 3:
                    mailing_list_ids |= set(item[2])
            if mailing_list_ids:
                value['mailing_domain'] = "[('list_id', 'in', %s)]" % list(
                    mailing_list_ids)
            else:
                value['mailing_domain'] = "[('list_id', '=', False)]"
        else:
            value['mailing_domain'] = False
        return {'value': value}

    def action_duplicate(self, cr, uid, ids, context=None):
        copy_id = None
        for mid in ids:
            copy_id = self.copy(cr, uid, mid, context=context)
        if copy_id:
            return {
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'mail.mass_mailing',
                'res_id': copy_id,
                'context': context,
            }
        return False

    def action_test_mailing(self, cr, uid, ids, context=None):
        ctx = dict(context, default_mass_mailing_id=ids[0])
        return {
            'name': _('Test Mailing'),
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'mail.mass_mailing.test',
            'target': 'new',
            'context': ctx,
        }

    def action_edit_html(self, cr, uid, ids, context=None):
        if not len(ids) == 1:
            raise ValueError('One and only one ID allowed for this action')
        mail = self.browse(cr, uid, ids[0], context=context)
        url = '/website_mail/email_designer?model=mail.mass_mailing&res_id=%d&template_model=%s&enable_editor=1' % (
            ids[0], mail.mailing_model)
        return {
            'name': _('Open with Visual Editor'),
            'type': 'ir.actions.act_url',
            'url': url,
            'target': 'self',
        }

    #------------------------------------------------------
    # Email Sending
    #------------------------------------------------------

    def get_recipients(self, cr, uid, mailing, context=None):
        if mailing.mailing_domain:
            domain = eval(mailing.mailing_domain)
            res_ids = self.pool[mailing.mailing_model].search(cr,
                                                              uid,
                                                              domain,
                                                              context=context)
        else:
            res_ids = []
            domain = [('id', 'in', res_ids)]

        # randomly choose a fragment
        if mailing.contact_ab_pc < 100:
            contact_nbr = self.pool[mailing.mailing_model].search(
                cr, uid, domain, count=True, context=context)
            topick = int(contact_nbr / 100.0 * mailing.contact_ab_pc)
            if mailing.mass_mailing_campaign_id and mailing.mass_mailing_campaign_id.unique_ab_testing:
                already_mailed = self.pool[
                    'mail.mass_mailing.campaign'].get_recipients(
                        cr,
                        uid, [mailing.mass_mailing_campaign_id.id],
                        context=context)[mailing.mass_mailing_campaign_id.id]
            else:
                already_mailed = set([])
            remaining = set(res_ids).difference(already_mailed)
            if topick > len(remaining):
                topick = len(remaining)
            res_ids = random.sample(remaining, topick)
        return res_ids

    def send_mail(self, cr, uid, ids, context=None):
        author_id = self.pool['res.users'].browse(
            cr, uid, uid, context=context).partner_id.id
        for mailing in self.browse(cr, uid, ids, context=context):
            # instantiate an email composer + send emails
            res_ids = self.get_recipients(cr, uid, mailing, context=context)
            if not res_ids:
                raise Warning('Please select recipients.')
            comp_ctx = dict(context, active_ids=res_ids)
            composer_values = {
                'author_id': author_id,
                'body': mailing.body_html,
                'subject': mailing.name,
                'model': mailing.mailing_model,
                'email_from': mailing.email_from,
                'record_name': False,
                'composition_mode': 'mass_mail',
                'mass_mailing_id': mailing.id,
                'mailing_list_ids':
                [(4, l.id) for l in mailing.contact_list_ids],
                'same_thread': mailing.reply_to_mode == 'thread',
            }
            if mailing.reply_to_mode == 'email':
                composer_values['reply_to'] = mailing.reply_to
            composer_id = self.pool['mail.compose.message'].create(
                cr, uid, composer_values, context=comp_ctx)
            self.pool['mail.compose.message'].send_mail(cr,
                                                        uid, [composer_id],
                                                        context=comp_ctx)
            self.write(cr,
                       uid, [mailing.id], {
                           'sent_date': fields.datetime.now(),
                           'state': 'done'
                       },
                       context=context)
        return True
예제 #8
0
class BlogPost(osv.Model):
    _name = "blog.post"
    _description = "Blog Post"
    _inherit = ['mail.thread', 'website.seo.metadata']
    _order = 'id DESC'

    def _compute_ranking(self, cr, uid, ids, name, arg, context=None):
        res = {}
        for blog_post in self.browse(cr, uid, ids, context=context):
            age = datetime.now() - datetime.strptime(
                blog_post.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
            res[blog_post.id] = blog_post.visits * (
                0.5 + random.random()) / max(3, age.days)
        return res

    _columns = {
        'name':
        fields.char('Title', required=True, translate=True),
        'subtitle':
        fields.char('Sub Title', translate=True),
        'author_id':
        fields.many2one('res.partner', 'Author'),
        'background_image':
        fields.binary('Background Image', oldname='content_image'),
        'blog_id':
        fields.many2one(
            'blog.blog',
            'Blog',
            required=True,
            ondelete='cascade',
        ),
        'tag_ids':
        fields.many2many(
            'blog.tag',
            string='Tags',
        ),
        'content':
        fields.html('Content', translate=True, sanitize=False),
        # website control
        'website_published':
        fields.boolean(
            'Publish',
            help="Publish on the website",
            copy=False,
        ),
        'website_message_ids':
        fields.one2many(
            'mail.message',
            'res_id',
            domain=lambda self: [
                '&', '&', ('model', '=', self._name), ('type', '=', 'comment'),
                ('path', '=', False)
            ],
            string='Website Messages',
            help="Website communication history",
        ),
        # creation / update stuff
        'create_date':
        fields.datetime(
            'Created on',
            select=True,
            readonly=True,
        ),
        'create_uid':
        fields.many2one(
            'res.users',
            'Author',
            select=True,
            readonly=True,
        ),
        'write_date':
        fields.datetime(
            'Last Modified on',
            select=True,
            readonly=True,
        ),
        'write_uid':
        fields.many2one(
            'res.users',
            'Last Contributor',
            select=True,
            readonly=True,
        ),
        'author_avatar':
        fields.related('author_id',
                       'image_small',
                       string="Avatar",
                       type="binary"),
        'visits':
        fields.integer('No of Views'),
        'ranking':
        fields.function(_compute_ranking, string='Ranking', type='float'),
    }

    _defaults = {
        'name':
        _('Blog Post Title'),
        'subtitle':
        _('Subtitle'),
        'author_id':
        lambda self, cr, uid, ctx=None: self.pool['res.users'].browse(
            cr, uid, uid, context=ctx).partner_id.id,
    }

    def html_tag_nodes(self, html, attribute=None, tags=None, context=None):
        """ Processing of html content to tag paragraphs and set them an unique
        ID.
        :return result: (html, mappin), where html is the updated html with ID
                        and mapping is a list of (old_ID, new_ID), where old_ID
                        is None is the paragraph is a new one. """
        mapping = []
        if not html:
            return html, mapping
        if tags is None:
            tags = ['p']
        if attribute is None:
            attribute = 'data-unique-id'
        counter = 0

        # form a tree
        root = lxml.html.fragment_fromstring(html, create_parent='div')
        if not len(root) and root.text is None and root.tail is None:
            return html, mapping

        # check all nodes, replace :
        # - img src -> check URL
        # - a href -> check URL
        for node in root.iter():
            if not node.tag in tags:
                continue
            ancestor_tags = [parent.tag for parent in node.iterancestors()]
            if ancestor_tags:
                ancestor_tags.pop()
            ancestor_tags.append('counter_%s' % counter)
            new_attribute = '/'.join(reversed(ancestor_tags))
            old_attribute = node.get(attribute)
            node.set(attribute, new_attribute)
            mapping.append((old_attribute, counter))
            counter += 1

        html = lxml.html.tostring(root, pretty_print=False, method='html')
        # this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
        if html.startswith('<div>') and html.endswith('</div>'):
            html = html[5:-6]
        return html, mapping

    def _postproces_content(self, cr, uid, id, content=None, context=None):
        if content is None:
            content = self.browse(cr, uid, id, context=context).content
        if content is False:
            return content
        content, mapping = self.html_tag_nodes(content,
                                               attribute='data-chatter-id',
                                               tags=['p'],
                                               context=context)
        for old_attribute, new_attribute in mapping:
            if not old_attribute:
                continue
            msg_ids = self.pool['mail.message'].search(
                cr,
                SUPERUSER_ID, [('path', '=', old_attribute)],
                context=context)
            self.pool['mail.message'].write(cr,
                                            SUPERUSER_ID,
                                            msg_ids, {'path': new_attribute},
                                            context=context)
        return content

    def _check_for_publication(self, cr, uid, ids, vals, context=None):
        if vals.get('website_published'):
            base_url = self.pool['ir.config_parameter'].get_param(
                cr, uid, 'web.base.url')
            for post in self.browse(cr, uid, ids, context=context):
                post.blog_id.message_post(
                    body=
                    '<p>%(post_publication)s <a href="%(base_url)s/blog/%(blog_slug)s/post/%(post_slug)s">%(post_link)s</a></p>'
                    % {
                        'post_publication':
                        _('A new post %s has been published on the %s blog.') %
                        (post.name, post.blog_id.name),
                        'post_link':
                        _('Click here to access the post.'),
                        'base_url':
                        base_url,
                        'blog_slug':
                        slug(post.blog_id),
                        'post_slug':
                        slug(post),
                    },
                    subtype='website_blog.mt_blog_blog_published',
                    context=context)
            return True
        return False

    def create(self, cr, uid, vals, context=None):
        if context is None:
            context = {}
        if 'content' in vals:
            vals['content'] = self._postproces_content(cr,
                                                       uid,
                                                       None,
                                                       vals['content'],
                                                       context=context)
        create_context = dict(context, mail_create_nolog=True)
        post_id = super(BlogPost, self).create(cr,
                                               uid,
                                               vals,
                                               context=create_context)
        self._check_for_publication(cr, uid, [post_id], vals, context=context)
        return post_id

    def write(self, cr, uid, ids, vals, context=None):
        if 'content' in vals:
            vals['content'] = self._postproces_content(cr,
                                                       uid,
                                                       None,
                                                       vals['content'],
                                                       context=context)
        result = super(BlogPost, self).write(cr, uid, ids, vals, context)
        self._check_for_publication(cr, uid, ids, vals, context=context)
        return result
예제 #9
0
class BlogPost(osv.Model):
    _name = "blog.post"
    _description = "Blog Post"
    _inherit = ['mail.thread', 'website.seo.metadata', 'website.published.mixin']
    _order = 'id DESC'
    _mail_post_access = 'read'

    def _website_url(self, cr, uid, ids, field_name, arg, context=None):
        res = super(BlogPost, self)._website_url(cr, uid, ids, field_name, arg, context=context)
        for blog_post in self.browse(cr, uid, ids, context=context):
            res[blog_post.id] = "/blog/%s/post/%s" % (slug(blog_post.blog_id), slug(blog_post))
        return res

    def _compute_ranking(self, cr, uid, ids, name, arg, context=None):
        res = {}
        for blog_post in self.browse(cr, uid, ids, context=context):
            age = datetime.now() - datetime.strptime(blog_post.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
            res[blog_post.id] = blog_post.visits * (0.5+random.random()) / max(3, age.days)
        return res

    def _default_content(self, cr, uid, context=None):
        return '''  <div class="container">
                        <section class="mt16 mb16">
                            <p class="o_default_snippet_text">''' + _("Start writing here...") + '''</p>
                        </section>
                    </div> '''

    _columns = {
        'name': fields.char('Title', required=True, translate=True),
        'subtitle': fields.char('Sub Title', translate=True),
        'author_id': fields.many2one('res.partner', 'Author'),
        'cover_properties': fields.text('Cover Properties'),
        'blog_id': fields.many2one(
            'blog.blog', 'Blog',
            required=True, ondelete='cascade',
        ),
        'tag_ids': fields.many2many(
            'blog.tag', string='Tags',
        ),
        'content': fields.html('Content', translate=True, sanitize=False),
        'website_message_ids': fields.one2many(
            'mail.message', 'res_id',
            domain=lambda self: [
                '&', '&', ('model', '=', self._name), ('message_type', '=', 'comment'), ('path', '=', False)
            ],
            string='Website Messages',
            help="Website communication history",
        ),
        # creation / update stuff
        'create_date': fields.datetime(
            'Created on',
            select=True, readonly=True,
        ),
        'create_uid': fields.many2one(
            'res.users', 'Author',
            select=True, readonly=True,
        ),
        'write_date': fields.datetime(
            'Last Modified on',
            select=True, readonly=True,
        ),
        'write_uid': fields.many2one(
            'res.users', 'Last Contributor',
            select=True, readonly=True,
        ),
        'author_avatar': fields.related(
            'author_id', 'image_small',
            string="Avatar", type="binary"),
        'visits': fields.integer('No of Views'),
        'ranking': fields.function(_compute_ranking, string='Ranking', type='float'),
    }

    _defaults = {
        'name': '',
        'content': _default_content,
        'cover_properties': '{"background-image": "none", "background-color": "oe_none", "opacity": "0.6", "resize_class": ""}',
        'author_id': lambda self, cr, uid, ctx=None: self.pool['res.users'].browse(cr, uid, uid, context=ctx).partner_id.id,
    }

    def html_tag_nodes(self, html, attribute=None, tags=None, context=None):
        """ Processing of html content to tag paragraphs and set them an unique
        ID.
        :return result: (html, mappin), where html is the updated html with ID
                        and mapping is a list of (old_ID, new_ID), where old_ID
                        is None is the paragraph is a new one. """

        existing_attributes = []
        mapping = []
        if not html:
            return html, mapping
        if tags is None:
            tags = ['p']
        if attribute is None:
            attribute = 'data-unique-id'

        # form a tree
        root = lxml.html.fragment_fromstring(html, create_parent='div')
        if not len(root) and root.text is None and root.tail is None:
            return html, mapping

        # check all nodes, replace :
        # - img src -> check URL
        # - a href -> check URL
        for node in root.iter():
            if node.tag not in tags:
                continue
            ancestor_tags = [parent.tag for parent in node.iterancestors()]

            old_attribute = node.get(attribute)
            new_attribute = old_attribute
            if not new_attribute or (old_attribute in existing_attributes):
                if ancestor_tags:
                    ancestor_tags.pop()
                counter = random.randint(10000, 99999)
                ancestor_tags.append('counter_%s' % counter)
                new_attribute = '/'.join(reversed(ancestor_tags))
                node.set(attribute, new_attribute)

            existing_attributes.append(new_attribute)
            mapping.append((old_attribute, new_attribute))

        html = lxml.html.tostring(root, pretty_print=False, method='html')
        # this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
        if html.startswith('<div>') and html.endswith('</div>'):
            html = html[5:-6]
        return html, mapping

    def _postproces_content(self, cr, uid, id, content=None, context=None):
        if content is None:
            content = self.browse(cr, uid, id, context=context).content
        if content is False:
            return content

        content, mapping = self.html_tag_nodes(content, attribute='data-chatter-id', tags=['p'], context=context)
        if id:  # not creating
            existing = [x[0] for x in mapping if x[0]]
            msg_ids = self.pool['mail.message'].search(cr, SUPERUSER_ID, [
                ('res_id', '=', id),
                ('model', '=', self._name),
                ('path', 'not in', existing),
                ('path', '!=', False)
            ], context=context)
            self.pool['mail.message'].unlink(cr, SUPERUSER_ID, msg_ids, context=context)

        return content

    def _check_for_publication(self, cr, uid, ids, vals, context=None):
        if vals.get('website_published'):
            base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
            for post in self.browse(cr, uid, ids, context=context):
                post.blog_id.message_post(
                    body='<p>%(post_publication)s <a href="%(base_url)s/blog/%(blog_slug)s/post/%(post_slug)s">%(post_link)s</a></p>' % {
                        'post_publication': _('A new post %s has been published on the %s blog.') % (post.name, post.blog_id.name),
                        'post_link': _('Click here to access the post.'),
                        'base_url': base_url,
                        'blog_slug': slug(post.blog_id),
                        'post_slug': slug(post),
                    },
                    subtype='website_blog.mt_blog_blog_published')
            return True
        return False

    def create(self, cr, uid, vals, context=None):
        if context is None:
            context = {}
        if 'content' in vals:
            vals['content'] = self._postproces_content(cr, uid, None, vals['content'], context=context)
        create_context = dict(context, mail_create_nolog=True)
        post_id = super(BlogPost, self).create(cr, uid, vals, context=create_context)
        self._check_for_publication(cr, uid, [post_id], vals, context=context)
        return post_id

    def write(self, cr, uid, ids, vals, context=None):
        if isinstance(ids, (int, long)):
            ids = [ids]
        if 'content' in vals:
            vals['content'] = self._postproces_content(cr, uid, ids[0], vals['content'], context=context)
        result = super(BlogPost, self).write(cr, uid, ids, vals, context)
        self._check_for_publication(cr, uid, ids, vals, context=context)
        return result

    def get_access_action(self, cr, uid, id, context=None):
        """ Override method that generated the link to access the document. Instead
        of the classic form view, redirect to the post on the website directly """
        post = self.browse(cr, uid, id, context=context)
        return {
            'type': 'ir.actions.act_url',
            'url': '/blog/%s/post/%s' % (post.blog_id.id, post.id),
            'target': 'self',
            'res_id': self.id,
        }

    def _notification_get_recipient_groups(self, cr, uid, ids, message, recipients, context=None):
        """ Override to set the access button: everyone can see an access button
        on their notification email. It will lead on the website view of the
        post. """
        res = super(BlogPost, self)._notification_get_recipient_groups(cr, uid, ids, message, recipients, context=context)
        access_action = self._notification_link_helper('view', model=message.model, res_id=message.res_id)
        for category, data in res.iteritems():
            res[category]['button_access'] = {'url': access_action, 'title': _('View Blog Post')}
        return res
예제 #10
0
class note_note(osv.osv):
    """ Note """
    _name = 'note.note'
    _inherit = ['mail.thread']
    _description = "Note"

    #writing method (no modification of values)
    def name_create(self, cr, uid, name, context=None):
        rec_id = self.create(cr, uid, {'memo': name}, context=context)
        return self.name_get(cr, uid, [rec_id], context)[0]

    #read the first line (convert hml into text)
    def _get_note_first_line(self,
                             cr,
                             uid,
                             ids,
                             name="",
                             args={},
                             context=None):
        res = {}
        for note in self.browse(cr, uid, ids, context=context):
            res[note.id] = (note.memo and html2plaintext(note.memo)
                            or "").strip().replace('*', '').split("\n")[0]

        return res

    def onclick_note_is_done(self, cr, uid, ids, context=None):
        return self.write(cr,
                          uid,
                          ids, {
                              'open': False,
                              'date_done': fields.date.today()
                          },
                          context=context)

    def onclick_note_not_done(self, cr, uid, ids, context=None):
        return self.write(cr, uid, ids, {'open': True}, context=context)

    #used for undisplay the follower if it's the current user
    def _get_my_current_partner(self, cr, uid, ids, name, args, context=None):
        user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
        pid = user.partner_id and user.partner_id.id or False
        return dict.fromkeys(ids, pid)

    #return the default stage for the uid user
    def _get_default_stage_id(self, cr, uid, context=None):
        ids = self.pool.get('note.stage').search(cr,
                                                 uid, [('user_id', '=', uid)],
                                                 context=context)
        return ids and ids[0] or False

    def _set_stage_per_user(self,
                            cr,
                            uid,
                            id,
                            name,
                            value,
                            args=None,
                            context=None):
        note = self.browse(cr, uid, id, context=context)
        if not value: return False
        stage_ids = [value] + [
            stage.id for stage in note.stage_ids if stage.user_id.id != uid
        ]
        return self.write(cr,
                          uid, [id], {'stage_ids': [(6, 0, set(stage_ids))]},
                          context=context)

    def _get_stage_per_user(self, cr, uid, ids, name, args, context=None):
        result = dict.fromkeys(ids, False)
        for record in self.browse(cr, uid, ids, context=context):
            for stage in record.stage_ids:
                if stage.user_id.id == uid:
                    result[record.id] = stage.id
        return result

    _columns = {
        'name':
        fields.function(_get_note_first_line,
                        string='Note Summary',
                        type='text',
                        store=True),
        'memo':
        fields.html('Note Content'),
        'sequence':
        fields.integer('Sequence'),
        'stage_id':
        fields.function(_get_stage_per_user,
                        fnct_inv=_set_stage_per_user,
                        string='Stage',
                        type='many2one',
                        relation='note.stage'),
        'stage_ids':
        fields.many2many('note.stage', 'note_stage_rel', 'note_id', 'stage_id',
                         'Stages of Users'),
        'open':
        fields.boolean('Active', track_visibility='onchange'),
        'date_done':
        fields.date('Date done'),
        'color':
        fields.integer('Color Index'),
        'tag_ids':
        fields.many2many('note.tag', 'note_tags_rel', 'note_id', 'tag_id',
                         'Tags'),
        'current_partner_id':
        fields.function(_get_my_current_partner,
                        type="many2one",
                        relation='res.partner',
                        string="Owner"),
    }
    _defaults = {
        'open': 1,
        'stage_id': _get_default_stage_id,
    }
    _order = 'sequence'

    def read_group(self,
                   cr,
                   uid,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   context=None,
                   orderby=False):
        if groupby and groupby[0] == "stage_id":

            #search all stages
            current_stage_ids = self.pool.get('note.stage').search(
                cr, uid, [('user_id', '=', uid)], context=context)

            if current_stage_ids:  #if the user have some stages

                #dict of stages: map les ids sur les noms
                stage_name = dict(
                    self.pool.get('note.stage').name_get(cr,
                                                         uid,
                                                         current_stage_ids,
                                                         context=context))

                result = [
                    {  #notes by stage for stages user
                        '__context': {
                            'group_by': groupby[1:]
                        },
                        '__domain':
                        domain + [('stage_ids.id', '=', current_stage_id)],
                        'stage_id':
                        (current_stage_id, stage_name[current_stage_id]),
                        'stage_id_count':
                        self.search(cr,
                                    uid,
                                    domain +
                                    [('stage_ids', '=', current_stage_id)],
                                    context=context,
                                    count=True)
                    } for current_stage_id in current_stage_ids
                ]

                #note without user's stage
                nb_notes_ws = self.search(
                    cr,
                    uid,
                    domain + [('stage_ids', 'not in', current_stage_ids)],
                    context=context,
                    count=True)
                if nb_notes_ws:
                    # add note to the first column if it's the first stage
                    dom_not_in = ('stage_ids', 'not in', current_stage_ids)
                    if result and result[0]['stage_id'][
                            0] == current_stage_ids[0]:
                        dom_in = result[0]['__domain'].pop()
                        result[0]['__domain'] = domain + [
                            '|', dom_in, dom_not_in
                        ]
                    else:
                        # add the first stage column
                        result = [{
                            '__context': {
                                'group_by': groupby[1:]
                            },
                            '__domain':
                            domain + [dom_not_in],
                            'stage_id': (current_stage_ids[0],
                                         stage_name[current_stage_ids[0]]),
                            'stage_id_count':
                            nb_notes_ws
                        }] + result

            else:  # if stage_ids is empty

                #note without user's stage
                nb_notes_ws = self.search(cr,
                                          uid,
                                          domain,
                                          context=context,
                                          count=True)
                if nb_notes_ws:
                    result = [{  #notes for unknown stage
                        '__context': {
                            'group_by': groupby[1:]
                        },
                        '__domain': domain,
                        'stage_id': False,
                        'stage_id_count': nb_notes_ws
                    }]
                else:
                    result = []
            return result

        else:
            return super(note_note, self).read_group(self,
                                                     cr,
                                                     uid,
                                                     domain,
                                                     fields,
                                                     groupby,
                                                     offset=offset,
                                                     limit=limit,
                                                     context=context,
                                                     orderby=orderby)
예제 #11
0
class PaymentAcquirer(osv.Model):
    """ Acquirer Model. Each specific acquirer can extend the model by adding
    its own fields, using the acquirer_name as a prefix for the new fields.
    Using the required_if_provider='<name>' attribute on fields it is possible
    to have required fields that depend on a specific acquirer.

    Each acquirer has a link to an ir.ui.view record that is a template of
    a button used to display the payment form. See examples in ``payment_ogone``
    and ``payment_paypal`` modules.

    Methods that should be added in an acquirer-specific implementation:

     - ``<name>_form_generate_values(self, cr, uid, id, reference, amount, currency,
       partner_id=False, partner_values=None, tx_custom_values=None, context=None)``:
       method that generates the values used to render the form button template.
     - ``<name>_get_form_action_url(self, cr, uid, id, context=None):``: method
       that returns the url of the button form. It is used for example in
       ecommerce application, if you want to post some data to the acquirer.
     - ``<name>_compute_fees(self, cr, uid, id, amount, currency_id, country_id,
       context=None)``: computed the fees of the acquirer, using generic fields
       defined on the acquirer model (see fields definition).

    Each acquirer should also define controllers to handle communication between
    OpenERP and the acquirer. It generally consists in return urls given to the
    button form and that the acquirer uses to send the customer back after the
    transaction, with transaction details given as a POST request.
    """
    _name = 'payment.acquirer'
    _description = 'Payment Acquirer'
    _order = 'sequence'

    def _get_providers(self, cr, uid, context=None):
        return []

    # indirection to ease inheritance
    _provider_selection = lambda self, *args, **kwargs: self._get_providers(*args, **kwargs)

    _columns = {
        'name': fields.char('Name', required=True, translate=True),
        'provider': fields.selection(_provider_selection, string='Provider', required=True),
        'company_id': fields.many2one('res.company', 'Company', required=True),
        'pre_msg': fields.html('Help Message', translate=True,
                               help='Message displayed to explain and help the payment process.'),
        'post_msg': fields.html('Thanks Message', help='Message displayed after having done the payment process.'),
        'view_template_id': fields.many2one('ir.ui.view', 'Form Button Template', required=True),
        'registration_view_template_id': fields.many2one('ir.ui.view', 'S2S Form Template',
                                                         domain=[('type', '=', 'qweb')],
                                                         help="Template for method registration"),
        'environment': fields.selection(
            [('test', 'Test'), ('prod', 'Production')],
            string='Environment', oldname='env'),
        'website_published': fields.boolean(
            'Visible in Portal / Website', copy=False,
            help="Make this payment acquirer available (Customer invoices, etc.)"),
        'auto_confirm': fields.selection(
            [('none', 'No automatic confirmation'),
             ('at_pay_confirm', 'At payment with acquirer confirmation'),
             ('at_pay_now', 'At payment no acquirer confirmation needed')],
            string='Order Confirmation', required=True),
        'pending_msg': fields.html('Pending Message', translate=True, help='Message displayed, if order is in pending state after having done the payment process.'),
        'done_msg': fields.html('Done Message', translate=True, help='Message displayed, if order is done successfully after having done the payment process.'),
        'cancel_msg': fields.html('Cancel Message', translate=True, help='Message displayed, if order is cancel during the payment process.'),
        'error_msg': fields.html('Error Message', translate=True, help='Message displayed, if error is occur during the payment process.'),
        # Fees
        'fees_active': fields.boolean('Add Extra Fees'),
        'fees_dom_fixed': fields.float('Fixed domestic fees'),
        'fees_dom_var': fields.float('Variable domestic fees (in percents)'),
        'fees_int_fixed': fields.float('Fixed international fees'),
        'fees_int_var': fields.float('Variable international fees (in percents)'),
        'sequence': fields.integer('Sequence', help="Determine the display order"),
    }

    image = openerp.fields.Binary("Image", attachment=True,
        help="This field holds the image used for this provider, limited to 1024x1024px")
    image_medium = openerp.fields.Binary("Medium-sized image",
        compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True,
        help="Medium-sized image of this provider. It is automatically "\
             "resized as a 128x128px image, with aspect ratio preserved. "\
             "Use this field in form views or some kanban views.")
    image_small = openerp.fields.Binary("Small-sized image",
        compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True,
        help="Small-sized image of this provider. It is automatically "\
             "resized as a 64x64px image, with aspect ratio preserved. "\
             "Use this field anywhere a small image is required.")

    @openerp.api.depends('image')
    def _compute_images(self):
        for rec in self:
            rec.image_medium = openerp.tools.image_resize_image_medium(rec.image)
            rec.image_small = openerp.tools.image_resize_image_small(rec.image)

    def _inverse_image_medium(self):
        for rec in self:
            rec.image = openerp.tools.image_resize_image_big(rec.image_medium)

    def _inverse_image_small(self):
        for rec in self:
            rec.image = openerp.tools.image_resize_image_big(rec.image_small)

    _defaults = {
        'company_id': lambda self, cr, uid, obj, ctx=None: self.pool['res.users'].browse(cr, uid, uid).company_id.id,
        'environment': 'prod',
        'website_published': False,
        'auto_confirm': 'at_pay_confirm',
        'pending_msg': '<i>Pending,</i> Your online payment has been successfully processed. But your order is not validated yet.',
        'done_msg': '<i>Done,</i> Your online payment has been successfully processed. Thank you for your order.',
        'cancel_msg': '<i>Cancel,</i> Your payment has been cancelled.',
        'error_msg': "<i>Error,</i> Please be aware that an error occurred during the transaction. The order has been confirmed but won't be paid. Don't hesitate to contact us if you have any questions on the status of your order."
    }

    def _check_required_if_provider(self, cr, uid, ids, context=None):
        """ If the field has 'required_if_provider="<provider>"' attribute, then it
        required if record.provider is <provider>. """
        for acquirer in self.browse(cr, uid, ids, context=context):
            if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()):
                return False
        return True

    _constraints = [
        (_check_required_if_provider, 'Required fields not filled', ['required for this provider']),
    ]

    def get_form_action_url(self, cr, uid, id, context=None):
        """ Returns the form action URL, for form-based acquirer implementations. """
        acquirer = self.browse(cr, uid, id, context=context)
        if hasattr(self, '%s_get_form_action_url' % acquirer.provider):
            return getattr(self, '%s_get_form_action_url' % acquirer.provider)(cr, uid, id, context=context)
        return False

    def render(self, cr, uid, id, reference, amount, currency_id, partner_id=False, values=None, context=None):
        """ Renders the form template of the given acquirer as a qWeb template.
        :param string reference: the transaction reference
        :param float amount: the amount the buyer has to pay
        :param currency_id: currency id
        :param dict partner_id: optional partner_id to fill values
        :param dict values: a dictionary of values for the transction that is
        given to the acquirer-specific method generating the form values
        :param dict context: OpenERP context

        All templates will receive:

         - acquirer: the payment.acquirer browse record
         - user: the current user browse record
         - currency_id: id of the transaction currency
         - amount: amount of the transaction
         - reference: reference of the transaction
         - partner_*: partner-related values
         - partner: optional partner browse record
         - 'feedback_url': feedback URL, controler that manage answer of the acquirer (without base url) -> FIXME
         - 'return_url': URL for coming back after payment validation (wihout base url) -> FIXME
         - 'cancel_url': URL if the client cancels the payment -> FIXME
         - 'error_url': URL if there is an issue with the payment -> FIXME
         - context: OpenERP context dictionary

        """
        if context is None:
            context = {}
        if values is None:
            values = {}
        acquirer = self.browse(cr, uid, id, context=context)

        # reference and amount
        values.setdefault('reference', reference)
        amount = float_round(amount, 2)
        values.setdefault('amount', amount)

        # currency id
        currency_id = values.setdefault('currency_id', currency_id)
        if currency_id:
            currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context)
        else:
            currency = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id
        values['currency'] = currency

        # Fill partner_* using values['partner_id'] or partner_id arguement
        partner_id = values.get('partner_id', partner_id)
        if partner_id:
            partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
            values.update({
                'partner': partner,
                'partner_id': partner_id,
                'partner_name': partner.name,
                'partner_lang': partner.lang,
                'partner_email': partner.email,
                'partner_zip': partner.zip,
                'partner_city': partner.city,
                'partner_address': _partner_format_address(partner.street, partner.street2),
                'partner_country_id': partner.country_id.id,
                'partner_country': partner.country_id,
                'partner_phone': partner.phone,
                'partner_state': partner.state_id,
            })
        if values.get('partner_name'):
            values.update({
                'partner_first_name': _partner_split_name(values.get('partner_name'))[0],
                'partner_last_name': _partner_split_name(values.get('partner_name'))[1],
            })

        # Fix address, country fields
        if not values.get('partner_address'):
            values['address'] = _partner_format_address(values.get('partner_street', ''), values.get('partner_street2', ''))
        if not values.get('partner_country') and values.get('partner_country_id'):
            values['country'] = self.pool['res.country'].browse(cr, uid, values.get('partner_country_id'), context=context)


        # compute fees
        fees_method_name = '%s_compute_fees' % acquirer.provider
        if hasattr(self, fees_method_name):
            fees = getattr(self, fees_method_name)(cr, uid, id, values['amount'], values['currency_id'], values['partner_country_id'], context=None)
            values['fees'] = float_round(fees, 2)

        # call <name>_form_generate_values to update the tx dict with acqurier specific values
        cust_method_name = '%s_form_generate_values' % (acquirer.provider)
        if hasattr(self, cust_method_name):
            method = getattr(self, cust_method_name)
            values = method(cr, uid, id, values, context=context)

        values.update({
            'tx_url': context.get('tx_url', self.get_form_action_url(cr, uid, id, context=context)),
            'submit_class': context.get('submit_class', 'btn btn-link'),
            'submit_txt': context.get('submit_txt'),
            'acquirer': acquirer,
            'user': self.pool.get("res.users").browse(cr, uid, uid, context=context),
            'context': context,
            'type': values.get('type') or 'form',
        })
        values.setdefault('return_url', False)

        # because render accepts view ids but not qweb -> need to use the xml_id
        return self.pool['ir.ui.view'].render(cr, uid, acquirer.view_template_id.xml_id, values, engine='ir.qweb', context=context)

    def _registration_render(self, cr, uid, id, partner_id, qweb_context=None, context=None):
        acquirer = self.browse(cr, uid, id, context=context)
        if qweb_context is None:
            qweb_context = {}
        qweb_context.update(id=id, partner_id=partner_id)
        method_name = '_%s_registration_form_generate_values' % (acquirer.provider,)
        if hasattr(self, method_name):
            method = getattr(self, method_name)
            qweb_context.update(method(cr, uid, id, qweb_context, context=context))
        return self.pool['ir.ui.view'].render(cr, uid, acquirer.registration_view_template_id.xml_id, qweb_context, engine='ir.qweb', context=context)

    def s2s_process(self, cr, uid, id, data, context=None):
        acquirer = self.browse(cr, uid, id, context=context)
        cust_method_name = '%s_s2s_form_process' % (acquirer.provider)
        if not self.s2s_validate(cr, uid, id, data, context=context):
            return False
        if hasattr(self, cust_method_name):
            method = getattr(self, cust_method_name)
            return method(cr, uid, data, context=context)
        return True

    def s2s_validate(self, cr, uid, id, data, context=None):
        acquirer = self.browse(cr, uid, id, context=context)
        cust_method_name = '%s_s2s_form_validate' % (acquirer.provider)
        if hasattr(self, cust_method_name):
            method = getattr(self, cust_method_name)
            return method(cr, uid, id, data, context=context)
        return True
예제 #12
0
class product_template(osv.Model):
    _inherit = "product.template"

    def _get_parallax_image(self, cr, uid, ids, name, args, context=None):
        result = dict.fromkeys(ids, False)
        for obj in self.browse(cr, uid, ids, context=context):
            result[obj.id] = tools.image_get_resized_images(
                obj.parallax_image,
                big_name='parallax_image',
                medium_name='parallax_image_medium',
                small_name='parallax_image_small',
                avoid_resize_medium=True)
        return result

    def _set_parallax_image(self,
                            cr,
                            uid,
                            id,
                            name,
                            value,
                            args,
                            context=None):
        return self.write(cr,
                          uid, [id], {'parallax_image': value},
                          context=context)

    def _get_square_image(self, cr, uid, ids, name, args, context=None):
        result = dict.fromkeys(ids, False)
        for obj in self.browse(cr, uid, ids, context=context):
            # need to return also an dict for the image like result[1] = {'image_square': base_64_data}
            result[obj.id] = {'image_square': False}
            if obj.image:
                result[obj.id] = {
                    'image_square':
                    resize_to_thumbnail(
                        img=obj.image,
                        box=(440, 440),
                    )
                }
        return result

    def _set_square_image(self, cr, uid, id, name, value, args, context=None):
        return self.write(cr, uid, [id], {'image': value}, context=context)

    # OVERRIDE orignal image functional fields to store full size images
    def _set_image(self, cr, uid, id, name, value, args, context=None):
        return self.write(cr, uid, [id], {'image': value}, context=context)

    def _get_image(self, cr, uid, ids, name, args, context=None):
        result = dict.fromkeys(ids, False)
        for obj in self.browse(cr, uid, ids, context=context):
            result[obj.id] = tools.image_get_resized_images(
                obj.image, avoid_resize_medium=True)
        return result

    _columns = {
        'hide_payment': fields.boolean('Hide complete Checkout Panel'),
        'hide_price': fields.boolean('Hide Price in Shop overview Pages'),
        'hide_quantity': fields.boolean('Hide Product-Quantity-Selector in CP'),
        'simple_checkout': fields.boolean('Simple Checkout'),
        'price_donate': fields.boolean('Arbitrary Price'),
        'price_donate_min': fields.integer(string='Minimum Arbitrary Price'),
        'payment_interval_ids': fields.many2many('product.payment_interval', string='Payment Intervals'),

        'hide_search': fields.boolean('Hide Search Field'),
        'hide_categories': fields.boolean('Hide Categories Navigation'),
        'hide_image': fields.boolean('Hide Image in Checkout Panel'),
        'hide_salesdesc': fields.boolean('Hide Text in Checkout Panel'),
        'hide_panelfooter': fields.boolean('Hide Checkout Panel Footer'),

        'show_desctop': fields.boolean('Show additional Description above Checkout Panel'),
        'show_descbottom': fields.boolean('Show additional Description below Checkout Panel'),

        'desc_short_top': fields.html(string='Banner Product Description - Top'),
        'desc_short': fields.html(string='Banner Product Description - Center'),
        'desc_short_bottom': fields.html(string='Banner Product Description - Bottom'),
        'image_square': fields.function(_get_square_image, fnct_inv=_set_square_image,
            string="Square Image (Auto crop and zoom)", type="binary", multi="_get_square_image",
            store={'product.template': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10)}),
        'parallax_image': fields.binary(string='Background Parallax Image'),
        'parallax_image_medium': fields.function(_get_parallax_image, fnct_inv=_set_parallax_image,
            string="Background Parallax Image", type="binary", multi="_get_parallax_image",
            store={
                'product.template': (lambda self, cr, uid, ids, c={}: ids, ['parallax_image'], 10),
            },
            help="Medium-sized image of the background. It is automatically "\
                 "resized as a 128x128px image, with aspect ratio preserved, "\
                 "only when the image exceeds one of those sizes. Use this field in form views or some kanban views."),
        'parallax_speed': fields.selection([('static', 'Static'), ('slow', 'Slow')], string='Parallax Speed'),
        # OVERRIDE orignal image functional fields to store full size images
        'image_medium': fields.function(_get_image, fnct_inv=_set_image,
            string="Medium-sized image", type="binary", multi="_get_image",
            store={
                'product.template': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
            },
            help="Medium-sized image of the product. It is automatically "\
                 "resized as a 128x128px image, with aspect ratio preserved, "\
                 "only when the image exceeds one of those sizes. Use this field in form views or some kanban views."),
        'image_small': fields.function(_get_image, fnct_inv=_set_image,
            string="Small-sized image", type="binary", multi="_get_image",
            store={
                'product.template': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
            },
            help="Small-sized image of the product. It is automatically "\
                 "resized as a 64x64px image, with aspect ratio preserved. "\
                 "Use this field anywhere a small image is required."),
    }
    _defaults = {
        'price_donate_min': 0,
        'parallax_speed': 'slow',
        'hide_quantity': True,
    }
예제 #13
0
class product_template(osv.Model):
    _inherit = 'product.template'

    def _sold_total(self, cr, uid, ids, field_name, arg, context=None):
        res = dict.fromkeys(ids, 0)
        for template in self.browse(cr, SUPERUSER_ID, ids, context=context):
            res[template.id] = sum(
                [p.sold_total for p in template.product_variant_ids])
        return res

    def _funding_reached(self, cr, uid, ids, field_name, arg, context=None):
        res = dict.fromkeys(ids, 0)
        for ptemplate in self.browse(cr, SUPERUSER_ID, ids, context=context):
            try:
                res[ptemplate.id] = int(
                    round(ptemplate.sold_total /
                          (ptemplate.funding_goal / 100)))
            except:
                res[ptemplate.id] = int(0)
        return res

    def action_view_sales_sold_total(self, cr, uid, ids, context=None):
        act_obj = self.pool.get('ir.actions.act_window')
        mod_obj = self.pool.get('ir.model.data')
        # find the related product.product ids
        product_ids = []
        for template in self.browse(cr, uid, ids, context=context):
            product_ids += [x.id for x in template.product_variant_ids]
        domain = [
            ('state', 'in', ["confirmed", "done"]),
            ('product_id', 'in', product_ids),
        ]
        # get the tree view
        result = mod_obj.xmlid_to_res_id(cr,
                                         uid,
                                         'sale.action_order_line_product_tree',
                                         raise_if_not_found=True)
        result = act_obj.read(cr, uid, [result], context=context)[0]
        # add the search domain
        result['domain'] = str(domain)
        return result

    # Hack because i could not find a way to browse res.partner.name in qweb template - always error 403 access rights
    # The positive side effect is better security since no one can browse res.partner fully!
    def _get_name(self, cr, uid, ids, flds, args, context=None):
        res = dict.fromkeys(ids, 0)
        for ptemplate in self.browse(cr, SUPERUSER_ID, ids, context=context):
            if ptemplate.funding_user:
                res[ptemplate.id] = ptemplate.funding_user.name
            else:
                res[ptemplate.id] = False
        return res

    _columns = {
        'sold_total':
        fields.function(_sold_total, string='# Sold Total', type='float'),
        'funding_goal':
        fields.float(string='Funding Goal'),
        'funding_desc':
        fields.html(string='Funding Description (HTML Field below Bar)'),
        'funding_reached':
        fields.function(_funding_reached,
                        string='Funding reached in %',
                        type='integer'),
        'funding_user':
        fields.many2one('res.partner', string='Funding-Campaign User'),
        'funding_user_name':
        fields.function(_get_name,
                        string="Funding-Campaign User Name",
                        type='char'),
        'hide_fundingtextinlist':
        fields.boolean('Hide Funding-Text in Overview-Pages'),
        'hide_fundingbarinlist':
        fields.boolean('Hide Funding-Bar in Overview-Pages'),
        'hide_fundingtextincp':
        fields.boolean('Hide Funding-Text in Checkout-Panel'),
        'hide_fundingbarincp':
        fields.boolean('Hide Funding-Bar in Checkout-Panel'),
        'hide_fundingtext':
        fields.boolean('Hide Funding-Text in Page'),
        'hide_fundingbar':
        fields.boolean('Hide Funding-Bar in Page'),
        'hide_fundingdesc':
        fields.boolean('Hide Funding-Description in Page'),
    }
예제 #14
0
class ebay_seller_list(osv.osv):
    _name = "ebay.seller.list"
    _description = "ebay sell"

    def _get_thumbnail(self, cr, uid, ids, field_name, arg, context):
        if context is None:
            context = {}
        res = {}
        for record in self.browse(cr, uid, ids, context=context):
            link = "http://thumbs3.ebaystatic.com/pict/%s8080.jpg" % record.item_id
            res[record.id] = base64.encodestring(urllib2.urlopen(link).read())
        return res

    _columns = {
        'buy_it_now_price':
        fields.float('Buy It Now Price'),
        'currency':
        fields.char('Currency ID', size=3),
        'hit_count':
        fields.integer('Hit Count', readonly=True),
        'item_id':
        fields.char('Item ID', size=38, readonly=True),

        # ListingDetails
        'end_time':
        fields.datetime('End Time', readonly=True),
        'start_time':
        fields.datetime('Start Time', readonly=True),
        'view_item_url':
        fields.char('View Item URL', readonly=True),
        'quantity':
        fields.char('Quantity'),

        # SellingStatus
        'quantity_sold':
        fields.integer('Quantity Sold', readonly=True),
        'start_price':
        fields.float('StartPrice'),
        'name':
        fields.char('Title', size=80),
        'watch_count':
        fields.integer('Watch Count', readonly=True),
        'user_id':
        fields.many2one('ebay.user', 'Seller', ondelete='cascade'),

        # Additional Info
        'thumbnail':
        fields.function(_get_thumbnail,
                        type='binary',
                        method=True,
                        string="Thumbnail"),
        'picture':
        fields.html('Picture', readonly=True),
        'average_monthly_sales':
        fields.integer('Average Monthly Sales', readonly=True),
    }

    _order = 'average_monthly_sales desc'

    def create_items(self, cr, uid, user, items, context=None):
        monthly_sales = 0
        monthly_sales_volume = 0
        now = datetime.now()
        for item in ebay_repeatable_list(items):
            if item.ListingType not in ('FixedPriceItem', 'StoresFixedPrice'):
                continue
            vals = dict()
            vals['buy_it_now_price'] = float(item.BuyItNowPrice.value)
            vals['currency'] = item.Currency
            vals['hit_count'] = item.HitCount if item.has_key(
                'HitCount') else 0
            vals['item_id'] = item.ItemID

            listing_details = item.ListingDetails
            vals['end_time'] = listing_details.EndTime
            start_time = listing_details.StartTime
            vals['start_time'] = start_time
            vals['view_item_url'] = listing_details.ViewItemURL

            vals['quantity'] = int(item.Quantity)

            selling_status = item.SellingStatus
            start_price = float(item.StartPrice.value)
            quantity_sold = int(selling_status.QuantitySold)
            vals['quantity_sold'] = quantity_sold
            vals['start_price'] = start_price

            vals['name'] = item.Title
            vals['watch_count'] = item.WatchCount if item.has_key(
                'WatchCount') else 0
            vals['user_id'] = user.id

            delta_days = (now - start_time).days
            if delta_days <= 0:
                delta_days = 1
            average_monthly_sales = quantity_sold * 30 / delta_days
            monthly_sales += start_price * average_monthly_sales
            monthly_sales_volume += average_monthly_sales

            vals['average_monthly_sales'] = average_monthly_sales

            if item.has_key(
                    'PictureDetails'
            ) and item.PictureDetails and item.PictureDetails.has_key(
                    'PictureURL'):
                picture_url = item.PictureDetails.PictureURL
                vals[
                    'picture'] = '<img src="%s" width="500"/>' % ebay_repeatable_list(
                        picture_url)[0]

            self.create(cr, uid, vals, context=context)
        return monthly_sales, monthly_sales_volume

    def get_seller_list_call(self,
                             cr,
                             uid,
                             user,
                             call_param,
                             parallel=None,
                             context=None):
        output_selector = [
            'HasMoreItems',
            'ItemArray.Item.BuyItNowPrice',
            'ItemArray.Item.Currency',
            'ItemArray.Item.ItemID',
            'ItemArray.Item.ListingDetails.ConvertedStartPrice',
            'ItemArray.Item.ListingDetails.StartTime',
            'ItemArray.Item.ListingDetails.EndTime',
            'ItemArray.Item.ListingDetails.ViewItemURL',
            'ItemArray.Item.ListingType',
            'ItemArray.Item.PictureDetails.PictureURL',
            'ItemArray.Item.PrimaryCategory',
            'ItemArray.Item.Quantity',
            'ItemArray.Item.SellingStatus.QuantitySold',
            'ItemArray.Item.StartPrice',
            'ItemArray.Item.Title',
            'ItemsPerPage',
            'PageNumber',
            'PaginationResult',
            'ReturnedItemCountActual',
        ]
        call_name = 'GetSellerList'
        call_data = dict()
        call_data['EndTimeFrom'] = call_param['end_time_from']
        call_data['EndTimeTo'] = call_param['end_time_to']
        call_data['IncludeWatchCount'] = True
        call_data['Pagination'] = {
            'EntriesPerPage': call_param['entries_per_page'],
            'PageNumber': call_param['page_number'],
        }
        call_data['UserID'] = user.name
        call_data['DetailLevel'] = 'ReturnAll'
        call_data['OutputSelector'] = output_selector

        api = self.pool.get('ebay.ebay').trading(cr,
                                                 uid,
                                                 user,
                                                 call_name,
                                                 parallel=parallel,
                                                 context=context)
        api.execute(call_name, call_data)
        return api

    def get_seller_list(self, cr, uid, user, context=None):
        last_updated = user.last_updated
        if last_updated:
            now_time = datetime.now()
            last_updated = datetime.strptime(
                last_updated, tools.DEFAULT_SERVER_DATETIME_FORMAT)
            delta = (now_time - last_updated).days
            if delta < 7:
                return True

        cr.execute(
            'delete from ebay_seller_list \
                        where user_id=%s', (user.id, ))

        now = datetime.now()
        end_time_from = now.isoformat()
        end_time_to = (now + timedelta(30)).isoformat()
        entries_per_page = 160
        page_number = 1

        call_param = dict(end_time_from=end_time_from,
                          end_time_to=end_time_to,
                          entries_per_page=entries_per_page,
                          page_number=page_number)

        reply = self.get_seller_list_call(cr,
                                          uid,
                                          user,
                                          call_param,
                                          context=context).response.reply
        total_number_of_pages = int(reply.PaginationResult.TotalNumberOfPages)

        if total_number_of_pages == 0:
            return True

        monthly_sales, monthly_sales_volume = self.create_items(
            cr, uid, user, reply.ItemArray.Item, context=context)

        page_number = 2
        total_number_of_pages += 1
        while page_number < total_number_of_pages:
            parallel = Parallel()
            multiple_threads = 0
            apis = list()
            while page_number < total_number_of_pages and multiple_threads < 5:
                call_param = dict(end_time_from=end_time_from,
                                  end_time_to=end_time_to,
                                  entries_per_page=entries_per_page,
                                  page_number=page_number)
                apis.append(
                    self.get_seller_list_call(cr,
                                              uid,
                                              user,
                                              call_param,
                                              parallel=parallel,
                                              context=context))
                page_number += 1
                multiple_threads += 1

            parallel.wait(120)

            for api in apis:
                reply = api.response.reply
                if reply.Ack in ('Success', 'Warning'):
                    _monthly_sales, _monthly_sales_volume = self.create_items(
                        cr, uid, user, reply.ItemArray.Item, context=context)
                    monthly_sales += _monthly_sales
                    monthly_sales_volume += _monthly_sales_volume
                else:
                    raise ConnectionError(api.error())

        return user.write(
            dict(last_updated=fields.datetime.now(),
                 monthly_sales=monthly_sales,
                 monthly_sales_volume=monthly_sales_volume))
예제 #15
0
class OEMName(models.Model):
    _name = 'mft.oem_name'

    _columns = {
        'name':
        fields.char(string='OEM Name',
                    readonly=True,
                    select=True,
                    states={'draft': [('readonly', False)]}),
        'language':
        fields.char(string='Language',
                    readonly=True,
                    select=True,
                    states={'draft': [('readonly', False)]}),
        'website':
        fields.text(string='Website',
                    readonly=True,
                    select=True,
                    states={'draft': [('readonly', False)]}),
        'oemname_env':
        fields.one2many('mft.oemname_env',
                        'oemname_id',
                        string='Self-Defined ENV',
                        readonly=True,
                        select=True,
                        states={'draft': [('readonly', False)]}),
        'description':
        fields.html(string='Description',
                    readonly=True,
                    select=True,
                    states={'draft': [('readonly', False)]}),
        'state':
        fields.selection([
            ('draft', 'Draft'),
            ('toconfirm', 'Request Confirm'),
            ('confirmed', 'Confirmed'),
            ('cancel', 'Cancel'),
        ],
                         'State',
                         readonly=True,
                         copy=False,
                         select=True),
        #'wo_ids': fields.one2many('mft.work_order','oem_id',string='成品工单')
    }

    _defaults = {'state': 'draft'}

    def action_toconfirm(self, cr, uid, ids, context=None):
        if context is None:
            context = {}
        self.write(cr, uid, ids, {'state': 'toconfirm'})
        return True

    def action_confirmed(self, cr, uid, ids, context=None):
        if context is None:
            context = {}
        self.write(cr, uid, ids, {'state': 'confirmed'})
        return True

    def action_recover(self, cr, uid, ids, context=None):
        if context is None:
            context = {}
        self.write(cr, uid, ids, {'state': 'draft'})
        return True

    def unlink(self, cr, uid, ids, context=None):
        product_names = self.read(cr, uid, ids, ['state'], context=context)
        unlink_ids = []
        for p in product_names:
            if p['state'] in ['draft']:
                unlink_ids.append(p['id'])
            else:
                raise osv.except_osv(_('Invalid Action!'),
                                     'Only draft can be deleted!')

        return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
예제 #16
0
class task(osv.osv):
    _name = "project.task"
    _description = "Task"
    _date_name = "date_start"
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _mail_post_access = 'read'

    def _get_default_partner(self, cr, uid, context=None):
        if context is None:
            context = {}
        if 'default_project_id' in context:
            project = self.pool.get('project.project').browse(cr, uid, context['default_project_id'], context=context)
            if project and project.partner_id:
                return project.partner_id.id
        return False

    def _get_default_stage_id(self, cr, uid, context=None):
        """ Gives default stage_id """
        if context is None:
            context = {}
        return self.stage_find(cr, uid, [], context.get('default_project_id'), [('fold', '=', False)], context=context)

    def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
        if context is None:
            context = {}
        stage_obj = self.pool.get('project.task.type')
        order = stage_obj._order
        access_rights_uid = access_rights_uid or uid
        if read_group_order == 'stage_id desc':
            order = '%s desc' % order
        if 'default_project_id' in context:
            search_domain = ['|', ('project_ids', '=', context['default_project_id']), ('id', 'in', ids)]
        else:
            search_domain = [('id', 'in', ids)]
        stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context)
        result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context)
        # restore order of the search
        result.sort(lambda x, y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))

        fold = {}
        for stage in stage_obj.browse(cr, access_rights_uid, stage_ids, context=context):
            fold[stage.id] = stage.fold or False
        return result, fold

    _group_by_full = {
        'stage_id': _read_group_stage_ids,
    }

    def onchange_remaining(self, cr, uid, ids, remaining=0.0, planned=0.0):
        if remaining and not planned:
            return {'value': {'planned_hours': remaining}}
        return {}

    def onchange_planned(self, cr, uid, ids, planned=0.0, effective=0.0):
        return {'value': {'remaining_hours': planned - effective}}

    def onchange_project(self, cr, uid, id, project_id, context=None):
        if project_id:
            project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
            if project and project.partner_id:
                return {'value': {'partner_id': project.partner_id.id}}
        return {'value': {'partner_id': False}}

    def onchange_user_id(self, cr, uid, ids, user_id, context=None):
        vals = {}
        if user_id:
            vals['date_start'] = fields.datetime.now()
        return {'value': vals}

    def duplicate_task(self, cr, uid, map_ids, context=None):
        mapper = lambda t: map_ids.get(t.id, t.id)
        for task in self.browse(cr, uid, map_ids.values(), context):
            new_child_ids = set(map(mapper, task.child_ids))
            new_parent_ids = set(map(mapper, task.parent_ids))
            if new_child_ids or new_parent_ids:
                task.write({'parent_ids': [(6,0,list(new_parent_ids))],
                            'child_ids':  [(6,0,list(new_child_ids))]})

    def copy_data(self, cr, uid, id, default=None, context=None):
        if default is None:
            default = {}
        if not default.get('name'):
            current = self.browse(cr, uid, id, context=context)
            default['name'] = _("%s (copy)") % current.name
        return super(task, self).copy_data(cr, uid, id, default, context)

    def _is_template(self, cr, uid, ids, field_name, arg, context=None):
        res = {}
        for task in self.browse(cr, uid, ids, context=context):
            res[task.id] = True
            if task.project_id:
                if task.project_id.active == False or task.project_id.state == 'template':
                    res[task.id] = False
        return res

    def _compute_displayed_image(self, cr, uid, ids, prop, arg, context=None):
        res = {}
        for line in self.browse(cr, uid, ids, context=context):
            res[line.id] = line.attachment_ids and line.attachment_ids.filtered(lambda x: x.file_type_icon == 'webimage')[0] or None
        return res

    _columns = {
        'active': fields.function(_is_template, store=True, string='Not a Template Task', type='boolean', help="This field is computed automatically and have the same behavior than the boolean 'active' field: if the task is linked to a template or unactivated project, it will be hidden unless specifically asked."),
        'name': fields.char('Task Title', track_visibility='onchange', size=128, required=True, select=True),
        'description': fields.html('Description'),
        'priority': fields.selection([('0','Normal'), ('1','High')], 'Priority', select=True),
        'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of tasks."),
        'stage_id': fields.many2one('project.task.type', 'Stage', track_visibility='onchange', select=True,
                        domain="[('project_ids', '=', project_id)]", copy=False),
        'tag_ids': fields.many2many('project.tags', string='Tags', oldname='categ_ids'),
        'kanban_state': fields.selection([('normal', 'In Progress'),('done', 'Ready for next stage'),('blocked', 'Blocked')], 'Kanban State',
                                         track_visibility='onchange',
                                         help="A task's kanban state indicates special situations affecting it:\n"
                                              " * Normal is the default situation\n"
                                              " * Blocked indicates something is preventing the progress of this task\n"
                                              " * Ready for next stage indicates the task is ready to be pulled to the next stage",
                                         required=True, copy=False),
        'create_date': fields.datetime('Create Date', readonly=True, select=True),
        'write_date': fields.datetime('Last Modification Date', readonly=True, select=True), #not displayed in the view but it might be useful with base_action_rule module (and it needs to be defined first for that)
        'date_start': fields.datetime('Starting Date', select=True, copy=False),
        'date_end': fields.datetime('Ending Date', select=True, copy=False),
        'date_assign': fields.datetime('Assigning Date', select=True, copy=False, readonly=True),
        'date_deadline': fields.date('Deadline', select=True, copy=False),
        'date_last_stage_update': fields.datetime('Last Stage Update', select=True, copy=False, readonly=True),
        'project_id': fields.many2one('project.project', 'Project', ondelete='set null', select=True, track_visibility='onchange', change_default=True),
        'parent_ids': fields.many2many('project.task', 'project_task_parent_rel', 'task_id', 'parent_id', 'Parent Tasks'),
        'child_ids': fields.many2many('project.task', 'project_task_parent_rel', 'parent_id', 'task_id', 'Delegated Tasks'),
        'notes': fields.text('Notes'),
        'planned_hours': fields.float('Initially Planned Hours', help='Estimated time to do the task, usually set by the project manager when the task is in draft state.'),
        'remaining_hours': fields.float('Remaining Hours', digits=(16,2), help="Total remaining time, can be re-estimated periodically by the assignee of the task."),
        'user_id': fields.many2one('res.users', 'Assigned to', select=True, track_visibility='onchange'),
        'partner_id': fields.many2one('res.partner', 'Customer'),
        'manager_id': fields.related('project_id', 'analytic_account_id', 'user_id', type='many2one', relation='res.users', string='Project Manager'),
        'company_id': fields.many2one('res.company', 'Company'),
        'id': fields.integer('ID', readonly=True),
        'color': fields.integer('Color Index'),
        'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True),
        'attachment_ids': fields.one2many('ir.attachment', 'res_id', domain=lambda self: [('res_model', '=', self._name)], auto_join=True, string='Attachments'),
        'displayed_image_id': fields.function(_compute_displayed_image, relation='ir.attachment', type="many2one", string='Attachment'),
        }
    _defaults = {
        'stage_id': _get_default_stage_id,
        'project_id': lambda self, cr, uid, ctx=None: ctx.get('default_project_id') if ctx is not None else False,
        'date_last_stage_update': fields.datetime.now,
        'kanban_state': 'normal',
        'priority': '0',
        'sequence': 10,
        'active': True,
        'user_id': lambda obj, cr, uid, ctx=None: uid,
        'company_id': lambda self, cr, uid, ctx=None: self.pool.get('res.company')._company_default_get(cr, uid, 'project.task', context=ctx),
        'partner_id': lambda self, cr, uid, ctx=None: self._get_default_partner(cr, uid, context=ctx),
        'date_start': fields.datetime.now,
    }
    _order = "priority desc, sequence, date_start, name, id"

    def _check_recursion(self, cr, uid, ids, context=None):
        for id in ids:
            visited_branch = set()
            visited_node = set()
            res = self._check_cycle(cr, uid, id, visited_branch, visited_node, context=context)
            if not res:
                return False

        return True

    def _check_cycle(self, cr, uid, id, visited_branch, visited_node, context=None):
        if id in visited_branch: #Cycle
            return False

        if id in visited_node: #Already tested don't work one more time for nothing
            return True

        visited_branch.add(id)
        visited_node.add(id)

        #visit child using DFS
        task = self.browse(cr, uid, id, context=context)
        for child in task.child_ids:
            res = self._check_cycle(cr, uid, child.id, visited_branch, visited_node, context=context)
            if not res:
                return False

        visited_branch.remove(id)
        return True

    def _check_dates(self, cr, uid, ids, context=None):
        if context == None:
            context = {}
        obj_task = self.browse(cr, uid, ids[0], context=context)
        start = obj_task.date_start or False
        end = obj_task.date_end or False
        if start and end :
            if start > end:
                return False
        return True

    _constraints = [
        (_check_recursion, 'Error ! You cannot create recursive tasks.', ['parent_ids']),
        (_check_dates, 'Error ! Task starting date must be lower than its ending date.', ['date_start','date_end'])
    ]

    # Override view according to the company definition
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
        users_obj = self.pool.get('res.users')
        if context is None: context = {}
        # read uom as admin to avoid access rights issues, e.g. for portal/share users,
        # this should be safe (no context passed to avoid side-effects)
        obj_tm = users_obj.browse(cr, SUPERUSER_ID, uid, context=context).company_id.project_time_mode_id
        tm = obj_tm and obj_tm.name or 'Hours'

        res = super(task, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)

        # read uom as admin to avoid access rights issues, e.g. for portal/share users,
        # this should be safe (no context passed to avoid side-effects)
        obj_tm = users_obj.browse(cr, SUPERUSER_ID, uid, context=context).company_id.project_time_mode_id
        try:
            # using get_object to get translation value
            uom_hour = self.pool['ir.model.data'].get_object(cr, uid, 'product', 'product_uom_hour', context=context)
        except ValueError:
            uom_hour = False
        if not obj_tm or not uom_hour or obj_tm.id == uom_hour.id:
            return res

        eview = etree.fromstring(res['arch'])

        # if the project_time_mode_id is not in hours (so in days), display it as a float field
        def _check_rec(eview):
            if eview.attrib.get('widget','') == 'float_time':
                eview.set('widget','float')
            for child in eview:
                _check_rec(child)
            return True

        _check_rec(eview)

        res['arch'] = etree.tostring(eview)

        # replace reference of 'Hours' to 'Day(s)'
        for f in res['fields']:
            # TODO this NOT work in different language than english
            # the field 'Initially Planned Hours' should be replaced by 'Initially Planned Days'
            # but string 'Initially Planned Days' is not available in translation
            if 'Hours' in res['fields'][f]['string']:
                res['fields'][f]['string'] = res['fields'][f]['string'].replace('Hours', obj_tm.name)
        return res

    def get_empty_list_help(self, cr, uid, help, context=None):
        context = dict(context or {})
        context['empty_list_help_id'] = context.get('default_project_id')
        context['empty_list_help_model'] = 'project.project'
        context['empty_list_help_document_name'] = _("tasks")
        return super(task, self).get_empty_list_help(cr, uid, help, context=context)

    # ----------------------------------------
    # Case management
    # ----------------------------------------

    def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None):
        """ Override of the base.stage method
            Parameter of the stage search taken from the lead:
            - section_id: if set, stages must belong to this section or
              be a default stage; if not set, stages must be default
              stages
        """
        if isinstance(cases, (int, long)):
            cases = self.browse(cr, uid, cases, context=context)
        # collect all section_ids
        section_ids = []
        if section_id:
            section_ids.append(section_id)
        for task in cases:
            if task.project_id:
                section_ids.append(task.project_id.id)
        search_domain = []
        if section_ids:
            search_domain = [('|')] * (len(section_ids) - 1)
            for section_id in section_ids:
                search_domain.append(('project_ids', '=', section_id))
        search_domain += list(domain)
        # perform search, return the first found
        stage_ids = self.pool.get('project.task.type').search(cr, uid, search_domain, order=order, context=context)
        if stage_ids:
            return stage_ids[0]
        return False

    def _check_child_task(self, cr, uid, ids, context=None):
        if context == None:
            context = {}
        tasks = self.browse(cr, uid, ids, context=context)
        for task in tasks:
            if task.child_ids:
                for child in task.child_ids:
                    if child.stage_id and not child.stage_id.fold:
                        raise UserError(_("Child task still open.\nPlease cancel or complete child task first."))
        return True


    def _store_history(self, cr, uid, ids, context=None):
        for task in self.browse(cr, uid, ids, context=context):
            self.pool.get('project.task.history').create(cr, uid, {
                'task_id': task.id,
                'remaining_hours': task.remaining_hours,
                'planned_hours': task.planned_hours,
                'kanban_state': task.kanban_state,
                'type_id': task.stage_id.id,
                'user_id': task.user_id.id

            }, context=context)
        return True

    # ------------------------------------------------
    # CRUD overrides
    # ------------------------------------------------

    def create(self, cr, uid, vals, context=None):
        context = dict(context or {})

        # for default stage
        if vals.get('project_id') and not context.get('default_project_id'):
            context['default_project_id'] = vals.get('project_id')
        # user_id change: update date_assign
        if vals.get('user_id'):
            vals['date_assign'] = fields.datetime.now()

        # context: no_log, because subtype already handle this
        create_context = dict(context, mail_create_nolog=True)
        task_id = super(task, self).create(cr, uid, vals, context=create_context)
        self._store_history(cr, uid, [task_id], context=context)
        return task_id

    def write(self, cr, uid, ids, vals, context=None):
        if isinstance(ids, (int, long)):
            ids = [ids]

        # stage change: update date_last_stage_update
        if 'stage_id' in vals:
            vals['date_last_stage_update'] = fields.datetime.now()
        # user_id change: update date_assign
        if vals.get('user_id'):
            vals['date_assign'] = fields.datetime.now()

        # Overridden to reset the kanban_state to normal whenever
        # the stage (stage_id) of the task changes.
        if vals and not 'kanban_state' in vals and 'stage_id' in vals:
            new_stage = vals.get('stage_id')
            vals_reset_kstate = dict(vals, kanban_state='normal')
            for t in self.browse(cr, uid, ids, context=context):
                write_vals = vals_reset_kstate if t.stage_id.id != new_stage else vals
                super(task, self).write(cr, uid, [t.id], write_vals, context=context)
            result = True
        else:
            result = super(task, self).write(cr, uid, ids, vals, context=context)

        if any(item in vals for item in ['stage_id', 'remaining_hours', 'user_id', 'kanban_state']):
            self._store_history(cr, uid, ids, context=context)
        return result

    def unlink(self, cr, uid, ids, context=None):
        if context is None:
            context = {}
        self._check_child_task(cr, uid, ids, context=context)
        res = super(task, self).unlink(cr, uid, ids, context)
        return res

    def _get_total_hours(self):
        return self.remaining_hours

    def _generate_task(self, cr, uid, tasks, ident=4, context=None):
        context = context or {}
        result = ""
        ident = ' '*ident
        company = self.pool["res.users"].browse(cr, uid, uid, context=context).company_id
        duration_uom = {
            'day(s)': 'd', 'days': 'd', 'day': 'd', 'd': 'd',
            'month(s)': 'm', 'months': 'm', 'month': 'month', 'm': 'm',
            'week(s)': 'w', 'weeks': 'w', 'week': 'w', 'w': 'w',
            'hour(s)': 'H', 'hours': 'H', 'hour': 'H', 'h': 'H',
        }.get(company.project_time_mode_id.name.lower(), "hour(s)")
        for task in tasks:
            if task.stage_id and task.stage_id.fold:
                continue
            result += '''
%sdef Task_%s():
%s  todo = \"%.2f%s\"
%s  effort = \"%.2f%s\"''' % (ident, task.id, ident, task.remaining_hours, duration_uom, ident, task._get_total_hours(), duration_uom)
            start = []
            for t2 in task.parent_ids:
                start.append("up.Task_%s.end" % (t2.id,))
            if start:
                result += '''
%s  start = max(%s)
''' % (ident,','.join(start))

            if task.user_id:
                result += '''
%s  resource = %s
''' % (ident, 'User_'+str(task.user_id.id))

        result += "\n"
        return result

    # ---------------------------------------------------
    # Mail gateway
    # ---------------------------------------------------

    def _track_subtype(self, cr, uid, ids, init_values, context=None):
        record = self.browse(cr, uid, ids[0], context=context)
        if 'kanban_state' in init_values and record.kanban_state == 'blocked':
            return 'project.mt_task_blocked'
        elif 'kanban_state' in init_values and record.kanban_state == 'done':
            return 'project.mt_task_ready'
        elif 'user_id' in init_values and record.user_id:  # assigned -> new
            return 'project.mt_task_new'
        elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence <= 1:  # start stage -> new
            return 'project.mt_task_new'
        elif 'stage_id' in init_values:
            return 'project.mt_task_stage'
        return super(task, self)._track_subtype(cr, uid, ids, init_values, context=context)

    @api.cr_uid_context
    def message_get_reply_to(self, cr, uid, ids, default=None, context=None):
        """ Override to get the reply_to of the parent project. """
        tasks = self.browse(cr, SUPERUSER_ID, ids, context=context)
        project_ids = set([task.project_id.id for task in tasks if task.project_id])
        aliases = self.pool['project.project'].message_get_reply_to(cr, uid, list(project_ids), default=default, context=context)
        return dict((task.id, aliases.get(task.project_id and task.project_id.id or 0, False)) for task in tasks)

    def message_new(self, cr, uid, msg, custom_values=None, context=None):
        """ Override to updates the document according to the email. """
        if custom_values is None:
            custom_values = {}
        defaults = {
            'name': msg.get('subject'),
            'planned_hours': 0.0,
            'partner_id': msg.get('author_id', False)
        }
        defaults.update(custom_values)
        res = super(task, self).message_new(cr, uid, msg, custom_values=defaults, context=context)
        email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or ''))
        new_task = self.browse(cr, uid, res, context=context)
        if new_task.project_id and new_task.project_id.alias_name:  # check left-part is not already an alias
            email_list = filter(lambda x: x.split('@')[0] != new_task.project_id.alias_name, email_list)
        partner_ids = filter(lambda x: x, self._find_partner_from_emails(cr, uid, email_list, check_followers=False))
        self.message_subscribe(cr, uid, [res], partner_ids, context=context)
        return res

    def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
        """ Override to update the task according to the email. """
        if update_vals is None:
            update_vals = {}
        maps = {
            'cost': 'planned_hours',
        }
        for line in msg['body'].split('\n'):
            line = line.strip()
            res = tools.command_re.match(line)
            if res:
                match = res.group(1).lower()
                field = maps.get(match)
                if field:
                    try:
                        update_vals[field] = float(res.group(2).lower())
                    except (ValueError, TypeError):
                        pass
        return super(task, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)

    def message_get_suggested_recipients(self, cr, uid, ids, context=None):
        recipients = super(task, self).message_get_suggested_recipients(cr, uid, ids, context=context)
        for data in self.browse(cr, uid, ids, context=context):
            if data.partner_id:
                reason = _('Customer Email') if data.partner_id.email else _('Customer')
                data._message_add_suggested_recipient(recipients, partner=data.partner_id, reason=reason)
        return recipients
예제 #17
0
class SaleOrder(orm.Model):
    """Adds condition to SO"""

    _inherit = "sale.order"
    _description = 'Sale Order'

    _columns = {
        'text_condition1':
        fields.many2one('sale.condition_text',
                        'Header',
                        domain=[('type', '=', 'header')]),
        'text_condition2':
        fields.many2one('sale.condition_text',
                        'Footer',
                        domain=[('type', '=', 'footer')]),
        'note1':
        fields.html('Header'),
        'note2':
        fields.html('Footer')
    }

    def _set_condition(self,
                       cursor,
                       uid,
                       inv_id,
                       commentid,
                       key,
                       partner_id=False):
        """Set the text of the notes in invoices"""
        if not commentid:
            return {}
        if not partner_id:
            raise osv.except_osv(
                _('No Customer Defined !'),
                _('Before choosing condition text select a customer.'))
        lang = self.pool.get('res.partner').browse(cursor, uid,
                                                   partner_id).lang or 'en_US'
        cond = self.pool.get('sale.condition_text').browse(
            cursor, uid, commentid, {'lang': lang})
        return {'value': {key: cond.text}}

    def set_header(self, cursor, uid, inv_id, commentid, partner_id=False):
        return self._set_condition(cursor, uid, inv_id, commentid, 'note1',
                                   partner_id)

    def set_footer(self, cursor, uid, inv_id, commentid, partner_id=False):
        return self._set_condition(cursor, uid, inv_id, commentid, 'note2',
                                   partner_id)

    def print_quotation(self, cursor, uid, ids, context=None):
        '''
        This function prints the sales order and mark it as sent,
        so that we can see more easily the next step of the workflow
        '''
        assert len(
            ids
        ) == 1, 'This option should only be used for a single id at a time'
        wf_service = netsvc.LocalService("workflow")
        wf_service.trg_validate(uid, 'sale.order', ids[0], 'quotation_sent',
                                cursor)
        datas = {
            'model': 'sale.order',
            'ids': ids,
            'form': self.read(cursor, uid, ids[0], context=context),
        }
        return {
            'type': 'ir.actions.report.xml',
            'report_name': 'sale.order.webkit',
            'datas': datas,
            'nodestroy': True
        }
예제 #18
0
class email_template(osv.osv):
    "Templates for sending email"
    _name = "email.template"
    _description = 'Email Templates'
    _order = 'name'

    def default_get(self, cr, uid, fields, context=None):
        res = super(email_template, self).default_get(cr, uid, fields, context)
        if res.get('model'):
            res['model_id'] = self.pool['ir.model'].search(cr, uid, [('model', '=', res.pop('model'))], context=context)[0]
        return res

    def _replace_local_links(self, cr, uid, html, context=None):
        """ Post-processing of html content to replace local links to absolute
        links, using web.base.url as base url. """
        if not html:
            return html

        # form a tree
        root = lxml.html.fromstring(html)
        if not len(root) and root.text is None and root.tail is None:
            html = '<div>%s</div>' % html
            root = lxml.html.fromstring(html)

        base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
        (base_scheme, base_netloc, bpath, bparams, bquery, bfragment) = urlparse.urlparse(base_url)

        def _process_link(url):
            new_url = url
            (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
            if not scheme and not netloc:
                new_url = urlparse.urlunparse((base_scheme, base_netloc, path, params, query, fragment))
            return new_url

        # check all nodes, replace :
        # - img src -> check URL
        # - a href -> check URL
        for node in root.iter():
            if node.tag == 'a' and node.get('href'):
                node.set('href', _process_link(node.get('href')))
            elif node.tag == 'img' and not node.get('src', 'data').startswith('data'):
                node.set('src', _process_link(node.get('src')))

        html = lxml.html.tostring(root, pretty_print=False, method='html')
        # this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
        if html.startswith('<div>') and html.endswith('</div>'):
            html = html[5:-6]
        return html

    def render_post_process(self, cr, uid, html, context=None):
        html = self._replace_local_links(cr, uid, html, context=context)
        return html

    def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
        """Render the given template text, replace mako expressions ``${expr}``
           with the result of evaluating these expressions with
           an evaluation context containing:

                * ``user``: browse_record of the current user
                * ``object``: browse_record of the document record this mail is
                              related to
                * ``context``: the context passed to the mail composition wizard

           :param str template: the template text to render
           :param str model: model name of the document record this mail is related to.
           :param int res_ids: list of ids of document records those mails are related to.
        """
        if context is None:
            context = {}
        res_ids = filter(None, res_ids)         # to avoid browsing [None] below
        results = dict.fromkeys(res_ids, u"")

        # try to load the template
        try:
            template = mako_template_env.from_string(tools.ustr(template))
        except Exception:
            _logger.exception("Failed to load template %r", template)
            return results

        # prepare template variables
        user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
        records = self.pool[model].browse(cr, uid, res_ids, context=context) or [None]
        variables = {
            'format_tz': lambda dt, tz=False, format=False, context=context: format_tz(self.pool, cr, uid, dt, tz, format, context),
            'user': user,
            'ctx': context,  # context kw would clash with mako internals
        }
        for record in records:
            res_id = record.id if record else None
            variables['object'] = record
            try:
                render_result = template.render(variables)
            except Exception:
                _logger.exception("Failed to render template %r using values %r" % (template, variables))
                render_result = u""
            if render_result == u"False":
                render_result = u""
            results[res_id] = render_result

        if post_process:
            for res_id, result in results.iteritems():
                results[res_id] = self.render_post_process(cr, uid, result, context=context)
        return results

    def get_email_template_batch(self, cr, uid, template_id=False, res_ids=None, context=None):
        if context is None:
            context = {}
        if res_ids is None:
            res_ids = [None]
        results = dict.fromkeys(res_ids, False)

        if not template_id:
            return results
        template = self.browse(cr, uid, template_id, context)
        langs = self.render_template_batch(cr, uid, template.lang, template.model, res_ids, context)
        for res_id, lang in langs.iteritems():
            if lang:
                # Use translated template if necessary
                ctx = context.copy()
                ctx['lang'] = lang
                template = self.browse(cr, uid, template.id, ctx)
            else:
                template = self.browse(cr, uid, int(template_id), context)
            results[res_id] = template
        return results

    def onchange_model_id(self, cr, uid, ids, model_id, context=None):
        mod_name = False
        if model_id:
            mod_name = self.pool.get('ir.model').browse(cr, uid, model_id, context).model
        return {'value': {'model': mod_name}}

    _columns = {
        'name': fields.char('Name'),
        'model_id': fields.many2one('ir.model', 'Applies to', help="The kind of document with with this template can be used"),
        'model': fields.related('model_id', 'model', type='char', string='Related Document Model',
                                 select=True, store=True, readonly=True),
        'lang': fields.char('Language',
                            help="Optional translation language (ISO code) to select when sending out an email. "
                                 "If not set, the english version will be used. "
                                 "This should usually be a placeholder expression "
                                 "that provides the appropriate language, e.g. "
                                 "${object.partner_id.lang}.",
                            placeholder="${object.partner_id.lang}"),
        'user_signature': fields.boolean('Add Signature',
                                         help="If checked, the user's signature will be appended to the text version "
                                              "of the message"),
        'subject': fields.char('Subject', translate=True, help="Subject (placeholders may be used here)",),
        'email_from': fields.char('From',
            help="Sender address (placeholders may be used here). If not set, the default "
                    "value will be the author's email alias if configured, or email address."),
        'use_default_to': fields.boolean(
            'Default recipients',
            help="Default recipients of the record:\n"
                 "- partner (using id on a partner or the partner_id field) OR\n"
                 "- email (using email_from or email field)"),
        'email_to': fields.char('To (Emails)', help="Comma-separated recipient addresses (placeholders may be used here)"),
        'partner_to': fields.char('To (Partners)',
            help="Comma-separated ids of recipient partners (placeholders may be used here)",
            oldname='email_recipients'),
        'email_cc': fields.char('Cc', help="Carbon copy recipients (placeholders may be used here)"),
        'reply_to': fields.char('Reply-To', help="Preferred response address (placeholders may be used here)"),
        'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
                                          help="Optional preferred server for outgoing mails. If not set, the highest "
                                               "priority one will be used."),
        'body_html': fields.html('Body', translate=True, sanitize=False, help="Rich-text/HTML version of the message (placeholders may be used here)"),
        'report_name': fields.char('Report Filename', translate=True,
                                   help="Name to use for the generated report file (may contain placeholders)\n"
                                        "The extension can be omitted and will then come from the report type."),
        'report_template': fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'),
        'ref_ir_act_window': fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True, copy=False,
                                            help="Sidebar action to make this template available on records "
                                                 "of the related document model"),
        'ref_ir_value': fields.many2one('ir.values', 'Sidebar Button', readonly=True, copy=False,
                                       help="Sidebar button to open the sidebar action"),
        'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
                                           'attachment_id', 'Attachments',
                                           help="You may attach files to this template, to be added to all "
                                                "emails created from this template"),
        'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it, to save space"),

        # Fake fields used to implement the placeholder assistant
        'model_object_field': fields.many2one('ir.model.fields', string="Field",
                                              help="Select target field from the related document model.\n"
                                                   "If it is a relationship field you will be able to select "
                                                   "a target field at the destination of the relationship."),
        'sub_object': fields.many2one('ir.model', 'Sub-model', readonly=True,
                                      help="When a relationship field is selected as first field, "
                                           "this field shows the document model the relationship goes to."),
        'sub_model_object_field': fields.many2one('ir.model.fields', 'Sub-field',
                                                  help="When a relationship field is selected as first field, "
                                                       "this field lets you select the target field within the "
                                                       "destination document model (sub-model)."),
        'null_value': fields.char('Default Value', help="Optional value to use if the target field is empty"),
        'copyvalue': fields.char('Placeholder Expression', help="Final placeholder expression, to be copy-pasted in the desired template field."),
    }

    _defaults = {
        'auto_delete': True,
    }

    def create_action(self, cr, uid, ids, context=None):
        action_obj = self.pool.get('ir.actions.act_window')
        data_obj = self.pool.get('ir.model.data')
        for template in self.browse(cr, uid, ids, context=context):
            src_obj = template.model_id.model
            model_data_id = data_obj._get_id(cr, uid, 'mail', 'email_compose_message_wizard_form')
            res_id = data_obj.browse(cr, uid, model_data_id, context=context).res_id
            button_name = _('Send Mail (%s)') % template.name
            act_id = action_obj.create(cr, SUPERUSER_ID, {
                 'name': button_name,
                 'type': 'ir.actions.act_window',
                 'res_model': 'mail.compose.message',
                 'src_model': src_obj,
                 'view_type': 'form',
                 'context': "{'default_composition_mode': 'mass_mail', 'default_template_id' : %d, 'default_use_template': True}" % (template.id),
                 'view_mode':'form,tree',
                 'view_id': res_id,
                 'target': 'new',
                 'auto_refresh':1
            }, context)
            ir_values_id = self.pool.get('ir.values').create(cr, SUPERUSER_ID, {
                 'name': button_name,
                 'model': src_obj,
                 'key2': 'client_action_multi',
                 'value': "ir.actions.act_window,%s" % act_id,
                 'object': True,
             }, context)

            template.write({
                'ref_ir_act_window': act_id,
                'ref_ir_value': ir_values_id,
            })

        return True

    def unlink_action(self, cr, uid, ids, context=None):
        for template in self.browse(cr, uid, ids, context=context):
            try:
                if template.ref_ir_act_window:
                    self.pool.get('ir.actions.act_window').unlink(cr, SUPERUSER_ID, template.ref_ir_act_window.id, context)
                if template.ref_ir_value:
                    ir_values_obj = self.pool.get('ir.values')
                    ir_values_obj.unlink(cr, SUPERUSER_ID, template.ref_ir_value.id, context)
            except Exception:
                raise osv.except_osv(_("Warning"), _("Deletion of the action record failed."))
        return True

    def unlink(self, cr, uid, ids, context=None):
        self.unlink_action(cr, uid, ids, context=context)
        return super(email_template, self).unlink(cr, uid, ids, context=context)

    def copy(self, cr, uid, id, default=None, context=None):
        template = self.browse(cr, uid, id, context=context)
        default = dict(default or {},
                       name=_("%s (copy)") % template.name)
        return super(email_template, self).copy(cr, uid, id, default, context)

    def build_expression(self, field_name, sub_field_name, null_value):
        """Returns a placeholder expression for use in a template field,
           based on the values provided in the placeholder assistant.

          :param field_name: main field name
          :param sub_field_name: sub field name (M2O)
          :param null_value: default value if the target value is empty
          :return: final placeholder expression
        """
        expression = ''
        if field_name:
            expression = "${object." + field_name
            if sub_field_name:
                expression += "." + sub_field_name
            if null_value:
                expression += " or '''%s'''" % null_value
            expression += "}"
        return expression

    def onchange_sub_model_object_value_field(self, cr, uid, ids, model_object_field, sub_model_object_field=False, null_value=None, context=None):
        result = {
            'sub_object': False,
            'copyvalue': False,
            'sub_model_object_field': False,
            'null_value': False
            }
        if model_object_field:
            fields_obj = self.pool.get('ir.model.fields')
            field_value = fields_obj.browse(cr, uid, model_object_field, context)
            if field_value.ttype in ['many2one', 'one2many', 'many2many']:
                res_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', field_value.relation)], context=context)
                sub_field_value = False
                if sub_model_object_field:
                    sub_field_value = fields_obj.browse(cr, uid, sub_model_object_field, context)
                if res_ids:
                    result.update({
                        'sub_object': res_ids[0],
                        'copyvalue': self.build_expression(field_value.name, sub_field_value and sub_field_value.name or False, null_value or False),
                        'sub_model_object_field': sub_model_object_field or False,
                        'null_value': null_value or False
                        })
            else:
                result.update({
                        'copyvalue': self.build_expression(field_value.name, False, null_value or False),
                        'null_value': null_value or False
                        })
        return {'value': result}

    def generate_recipients_batch(self, cr, uid, results, template_id, res_ids, context=None):
        """Generates the recipients of the template. Default values can ben generated
        instead of the template values if requested by template or context.
        Emails (email_to, email_cc) can be transformed into partners if requested
        in the context. """
        if context is None:
            context = {}
        template = self.browse(cr, uid, template_id, context=context)

        if template.use_default_to or context.get('tpl_force_default_to'):
            ctx = dict(context, thread_model=template.model)
            default_recipients = self.pool['mail.thread'].message_get_default_recipients(cr, uid, res_ids, context=ctx)
            for res_id, recipients in default_recipients.iteritems():
                results[res_id].pop('partner_to', None)
                results[res_id].update(recipients)

        for res_id, values in results.iteritems():
            partner_ids = values.get('partner_ids', list())
            if context and context.get('tpl_partners_only'):
                mails = tools.email_split(values.pop('email_to', '')) + tools.email_split(values.pop('email_cc', ''))
                for mail in mails:
                    partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
                    partner_ids.append(partner_id)
            partner_to = values.pop('partner_to', '')
            if partner_to:
                # placeholders could generate '', 3, 2 due to some empty field values
                tpl_partner_ids = [int(pid) for pid in partner_to.split(',') if pid]
                partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)
            results[res_id]['partner_ids'] = partner_ids
        return results

    def generate_email_batch(self, cr, uid, template_id, res_ids, context=None, fields=None):
        """Generates an email from the template for given the given model based on
        records given by res_ids.

        :param template_id: id of the template to render.
        :param res_id: id of the record to use for rendering the template (model
                       is taken from template definition)
        :returns: a dict containing all relevant fields for creating a new
                  mail.mail entry, with one extra key ``attachments``, in the
                  format [(report_name, data)] where data is base64 encoded.
        """
        if context is None:
            context = {}
        if fields is None:
            fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']

        report_xml_pool = self.pool.get('ir.actions.report.xml')
        res_ids_to_templates = self.get_email_template_batch(cr, uid, template_id, res_ids, context)

        # templates: res_id -> template; template -> res_ids
        templates_to_res_ids = {}
        for res_id, template in res_ids_to_templates.iteritems():
            templates_to_res_ids.setdefault(template, []).append(res_id)

        results = dict()
        for template, template_res_ids in templates_to_res_ids.iteritems():
            # generate fields value for all res_ids linked to the current template
            ctx = context.copy()
            if template.lang:
                ctx['lang'] = template._context.get('lang')
            for field in fields:
                generated_field_values = self.render_template_batch(
                    cr, uid, getattr(template, field), template.model, template_res_ids,
                    post_process=(field == 'body_html'),
                    context=ctx)
                for res_id, field_value in generated_field_values.iteritems():
                    results.setdefault(res_id, dict())[field] = field_value
            # compute recipients
            results = self.generate_recipients_batch(cr, uid, results, template.id, template_res_ids, context=context)
            # update values for all res_ids
            for res_id in template_res_ids:
                values = results[res_id]
                # body: add user signature, sanitize
                if 'body_html' in fields and template.user_signature:
                    signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
                    if signature:
                        values['body_html'] = tools.append_content_to_html(values['body_html'], signature, plaintext=False)
                if values.get('body_html'):
                    values['body'] = tools.html_sanitize(values['body_html'])
                # technical settings
                values.update(
                    mail_server_id=template.mail_server_id.id or False,
                    auto_delete=template.auto_delete,
                    model=template.model,
                    res_id=res_id or False,
                    attachment_ids=[attach.id for attach in template.attachment_ids],
                )

            # Add report in attachments: generate once for all template_res_ids
            if template.report_template:
                # Fix : Force report to use res ids and not active_ids
                if ctx and 'active_ids' in ctx:
                    del ctx['active_ids']
                for res_id in template_res_ids:
                    attachments = []
                    report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=ctx)
                    report = report_xml_pool.browse(cr, uid, template.report_template.id, context)
                    report_service = report.report_name

                    if report.report_type in ['qweb-html', 'qweb-pdf']:
                        result, format = self.pool['report'].get_pdf(cr, uid, [res_id], report_service, context=ctx), 'pdf'
                    else:
                        result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx)
            
            	    # TODO in trunk, change return format to binary to match message_post expected format
                    result = base64.b64encode(result)
                    if not report_name:
                        report_name = 'report.' + report_service
                    ext = "." + format
                    if not report_name.endswith(ext):
                        report_name += ext
                    attachments.append((report_name, result))
                    results[res_id]['attachments'] = attachments

        return results

    @api.cr_uid_id_context
    def send_mail(self, cr, uid, template_id, res_id, force_send=False, raise_exception=False, context=None):
        """Generates a new mail message for the given template and record,
           and schedules it for delivery through the ``mail`` module's scheduler.

           :param int template_id: id of the template to render
           :param int res_id: id of the record to render the template with
                              (model is taken from the template)
           :param bool force_send: if True, the generated mail.message is
                immediately sent after being created, as if the scheduler
                was executed for this message only.
           :returns: id of the mail.message that was created
        """
        if context is None:
            context = {}
        mail_mail = self.pool.get('mail.mail')
        ir_attachment = self.pool.get('ir.attachment')

        # create a mail_mail based on values, without attachments
        values = self.generate_email(cr, uid, template_id, res_id, context=context)
        if not values.get('email_from'):
            raise osv.except_osv(_('Warning!'), _("Sender email is missing or empty after template rendering. Specify one to deliver your message"))
        values['recipient_ids'] = [(4, pid) for pid in values.get('partner_ids', list())]
        attachment_ids = values.pop('attachment_ids', [])
        attachments = values.pop('attachments', [])
        msg_id = mail_mail.create(cr, uid, values, context=context)
        mail = mail_mail.browse(cr, uid, msg_id, context=context)

        # manage attachments
        for attachment in attachments:
            attachment_data = {
                'name': attachment[0],
                'datas_fname': attachment[0],
                'datas': attachment[1],
                'res_model': 'mail.message',
                'res_id': mail.mail_message_id.id,
            }
            context = dict(context)
            context.pop('default_type', None)
            attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context))
        if attachment_ids:
            values['attachment_ids'] = [(6, 0, attachment_ids)]
            mail_mail.write(cr, uid, msg_id, {'attachment_ids': [(6, 0, attachment_ids)]}, context=context)

        if force_send:
            mail_mail.send(cr, uid, [msg_id], raise_exception=raise_exception, context=context)
        return msg_id

    # Compatibility method
    def render_template(self, cr, uid, template, model, res_id, context=None):
        return self.render_template_batch(cr, uid, template, model, [res_id], context)[res_id]

    def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None):
        return self.get_email_template_batch(cr, uid, template_id, [record_id], context)[record_id]

    def generate_email(self, cr, uid, template_id, res_id, context=None):
        return self.generate_email_batch(cr, uid, template_id, [res_id], context=context)[res_id]
예제 #19
0
class PaymentTxOgone(osv.Model):
    _inherit = 'payment.transaction'
    # ogone status
    _ogone_valid_tx_status = [5, 9]
    _ogone_wait_tx_status = [41, 50, 51, 52, 55, 56, 91, 92, 99]
    _ogone_pending_tx_status = [46]  # 3DS HTML response
    _ogone_cancel_tx_status = [1]

    _columns = {
        'ogone_3ds': fields.boolean('3DS Activated'),
        'ogone_3ds_html': fields.html('3DS HTML'),
        'ogone_complus': fields.char('Complus'),
        'ogone_payid': fields.char('PayID',
                                   help='Payment ID, generated by Ogone')
    }

    # --------------------------------------------------
    # FORM RELATED METHODS
    # --------------------------------------------------

    def _ogone_form_get_tx_from_data(self, cr, uid, data, context=None):
        """ Given a data dict coming from ogone, verify it and find the related
        transaction record. """
        reference, pay_id, shasign = data.get('orderID'), data.get(
            'PAYID'), data.get('SHASIGN')
        if not reference or not pay_id or not shasign:
            error_msg = 'Ogone: received data with missing reference (%s) or pay_id (%s) or shashign (%s)' % (
                reference, pay_id, shasign)
            _logger.error(error_msg)
            raise ValidationError(error_msg)

        # find tx -> @TDENOTE use paytid ?
        tx_ids = self.search(cr,
                             uid, [('reference', '=', reference)],
                             context=context)
        if not tx_ids or len(tx_ids) > 1:
            error_msg = 'Ogone: received data for reference %s' % (reference)
            if not tx_ids:
                error_msg += '; no order found'
            else:
                error_msg += '; multiple order found'
            _logger.error(error_msg)
            raise ValidationError(error_msg)
        tx = self.pool['payment.transaction'].browse(cr,
                                                     uid,
                                                     tx_ids[0],
                                                     context=context)

        # verify shasign
        shasign_check = self.pool['payment.acquirer']._ogone_generate_shasign(
            tx.acquirer_id, 'out', data)
        if shasign_check.upper() != shasign.upper():
            error_msg = 'Ogone: invalid shasign, received %s, computed %s, for data %s' % (
                shasign, shasign_check, data)
            _logger.error(error_msg)
            raise ValidationError(error_msg)

        return tx

    def _ogone_form_get_invalid_parameters(self,
                                           cr,
                                           uid,
                                           tx,
                                           data,
                                           context=None):
        invalid_parameters = []

        # TODO: txn_id: should be false at draft, set afterwards, and verified with txn details
        if tx.acquirer_reference and data.get(
                'PAYID') != tx.acquirer_reference:
            invalid_parameters.append(
                ('PAYID', data.get('PAYID'), tx.acquirer_reference))
        # check what is bought
        if float_compare(float(data.get('amount', '0.0')), tx.amount, 2) != 0:
            invalid_parameters.append(
                ('amount', data.get('amount'), '%.2f' % tx.amount))
        if data.get('currency') != tx.currency_id.name:
            invalid_parameters.append(
                ('currency', data.get('currency'), tx.currency_id.name))

        return invalid_parameters

    def _ogone_form_validate(self, cr, uid, tx, data, context=None):
        if tx.state == 'done':
            _logger.warning(
                'Ogone: trying to validate an already validated tx (ref %s)' %
                tx.reference)
            return True

        status = int(data.get('STATUS', '0'))
        if status in self._ogone_valid_tx_status:
            tx.write({
                'state':
                'done',
                'date_validate':
                datetime.strptime(
                    data['TRXDATE'],
                    '%m/%d/%y').strftime(DEFAULT_SERVER_DATE_FORMAT),
                'acquirer_reference':
                data['PAYID'],
            })
            return True
        elif status in self._ogone_cancel_tx_status:
            tx.write({
                'state': 'cancel',
                'acquirer_reference': data.get('PAYID'),
            })
        elif status in self._ogone_pending_tx_status or status in self._ogone_wait_tx_status:
            tx.write({
                'state': 'pending',
                'acquirer_reference': data.get('PAYID'),
            })
        else:
            error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % {
                'error_str': data.get('NCERRORPLUS'),
                'error_code': data.get('NCERROR'),
                'error_msg': ogone.OGONE_ERROR_MAP.get(data.get('NCERROR')),
            }
            _logger.info(error)
            tx.write({
                'state': 'error',
                'state_message': error,
                'acquirer_reference': data.get('PAYID'),
            })
            return False

    # --------------------------------------------------
    # S2S RELATED METHODS
    # --------------------------------------------------

    def ogone_s2s_create_alias(self, cr, uid, id, values, context=None):
        """ Create an alias at Ogone via batch.

         .. versionadded:: pre-v8 saas-3
         .. warning::

            Experimental code. You should not use it before OpenERP v8 official
            release.
        """
        tx = self.browse(cr, uid, id, context=context)
        assert tx.type == 'server2server', 'Calling s2s dedicated method for a %s acquirer' % tx.type
        alias = 'OPENERP-%d-%d' % (tx.partner_id.id, tx.id)

        expiry_date = '%s%s' % (values['expiry_date_mm'],
                                values['expiry_date_yy'][2:])
        line = 'ADDALIAS;%(alias)s;%(holder_name)s;%(number)s;%(expiry_date)s;%(brand)s;%(pspid)s'
        line = line % dict(values,
                           alias=alias,
                           expiry_date=expiry_date,
                           pspid=tx.acquirer_id.ogone_pspid)

        tx_data = {
            'FILE_REFERENCE':
            'OPENERP-NEW-ALIAS-%s' % time.time(),  # something unique,
            'TRANSACTION_CODE': 'ATR',
            'OPERATION': 'SAL',
            'NB_PAYMENTS':
            1,  # even if we do not actually have any payment, ogone want it to not be 0
            'FILE': line,
            'REPLY_TYPE': 'XML',
            'PSPID': tx.acquirer_id.ogone_pspid,
            'USERID': tx.acquirer_id.ogone_userid,
            'PSWD': tx.acquirer_id.ogone_password,
            'PROCESS_MODE': 'CHECKANDPROCESS',
        }

        # TODO: fix URL computation
        request = urllib2.Request(tx.acquirer_id.ogone_afu_agree_url,
                                  urlencode(tx_data))
        result = urllib2.urlopen(request).read()

        try:
            tree = objectify.fromstring(result)
        except etree.XMLSyntaxError:
            _logger.exception('Invalid xml response from ogone')
            return None

        error_code = error_str = None
        if hasattr(tree, 'PARAMS_ERROR'):
            error_code = tree.NCERROR.text
            error_str = 'PARAMS ERROR: %s' % (tree.PARAMS_ERROR.text or '', )
        else:
            node = tree.FORMAT_CHECK
            error_node = getattr(node, 'FORMAT_CHECK_ERROR', None)
            if error_node is not None:
                error_code = error_node.NCERROR.text
                error_str = 'CHECK ERROR: %s' % (error_node.ERROR.text or '', )

        if error_code:
            error_msg = ogone.OGONE_ERROR_MAP.get(error_code)
            error = '%s\n\n%s: %s' % (error_str, error_code, error_msg)
            _logger.error(error)
            raise Exception(error)  # TODO specific exception

        tx.write({'partner_reference': alias})
        return True

    def ogone_s2s_generate_values(self,
                                  cr,
                                  uid,
                                  id,
                                  custom_values,
                                  context=None):
        """ Generate valid Ogone values for a s2s tx.

         .. versionadded:: pre-v8 saas-3
         .. warning::

            Experimental code. You should not use it before OpenERP v8 official
            release.
        """
        tx = self.browse(cr, uid, id, context=context)
        tx_data = {
            'PSPID': tx.acquirer_id.ogone_pspid,
            'USERID': tx.acquirer_id.ogone_userid,
            'PSWD': tx.acquirer_id.ogone_password,
            'OrderID': tx.reference,
            'amount': '%d' % int(float_round(tx.amount, 2) *
                                 100),  # tde check amount or str * 100 ?
            'CURRENCY': tx.currency_id.name,
            'LANGUAGE': tx.partner_lang,
            'OPERATION': 'SAL',
            'ECI': 2,  # Recurring (from MOTO)
            'ALIAS': tx.partner_reference,
            'RTIMEOUT': 30,
        }
        if custom_values.get('ogone_cvc'):
            tx_data['CVC'] = custom_values.get('ogone_cvc')
        if custom_values.pop('ogone_3ds', None):
            tx_data.update({
                'FLAG3D': 'Y',  # YEAH!!
            })
            if custom_values.get('ogone_complus'):
                tx_data['COMPLUS'] = custom_values.get('ogone_complus')
            if custom_values.get('ogone_accept_url'):
                pass

        shasign = self.pool['payment.acquirer']._ogone_generate_shasign(
            tx.acquirer_id, 'in', tx_data)
        tx_data['SHASIGN'] = shasign
        return tx_data

    def ogone_s2s_feedback(self, cr, uid, data, context=None):
        """
         .. versionadded:: pre-v8 saas-3
         .. warning::

            Experimental code. You should not use it before OpenERP v8 official
            release.
        """
        pass

    def ogone_s2s_execute(self, cr, uid, id, values, context=None):
        """
         .. versionadded:: pre-v8 saas-3
         .. warning::

            Experimental code. You should not use it before OpenERP v8 official
            release.
        """
        tx = self.browse(cr, uid, id, context=context)

        tx_data = self.ogone_s2s_generate_values(cr,
                                                 uid,
                                                 id,
                                                 values,
                                                 context=context)
        _logger.debug('Generated Ogone s2s data %s', pformat(tx_data))

        request = urllib2.Request(tx.acquirer_id.ogone_direct_order_url,
                                  urlencode(tx_data))
        result = urllib2.urlopen(request).read()
        _logger.debug('Contacted Ogone direct order; result %s', result)

        tree = objectify.fromstring(result)
        payid = tree.get('PAYID')

        query_direct_data = dict(
            PSPID=tx.acquirer_id.ogone_pspid,
            USERID=tx.acquirer_id.ogone_userid,
            PSWD=tx.acquirer_id.ogone_password,
            ID=payid,
        )
        query_direct_url = 'https://secure.ogone.com/ncol/%s/querydirect.asp' % (
            tx.acquirer_id.environment, )

        tries = 2
        tx_done = False
        tx_status = False
        while not tx_done or tries > 0:
            try:
                tree = objectify.fromstring(result)
            except etree.XMLSyntaxError:
                # invalid response from ogone
                _logger.exception('Invalid xml response from ogone')
                raise

            # see https://secure.ogone.com/ncol/paymentinfos1.asp
            VALID_TX = [5, 9]
            WAIT_TX = [41, 50, 51, 52, 55, 56, 91, 92, 99]
            PENDING_TX = [46]  # 3DS HTML response
            # other status are errors...

            status = tree.get('STATUS')
            if status == '':
                status = None
            else:
                status = int(status)

            if status in VALID_TX:
                tx_status = True
                tx_done = True

            elif status in PENDING_TX:
                html = str(tree.HTML_ANSWER)
                tx_data.update(ogone_3ds_html=html.decode('base64'))
                tx_status = False
                tx_done = True

            elif status in WAIT_TX:
                time.sleep(1500)

                request = urllib2.Request(query_direct_url,
                                          urlencode(query_direct_data))
                result = urllib2.urlopen(request).read()
                _logger.debug('Contacted Ogone query direct; result %s',
                              result)

            else:
                error_code = tree.get('NCERROR')
                if not ogone.retryable(error_code):
                    error_str = tree.get('NCERRORPLUS')
                    error_msg = ogone.OGONE_ERROR_MAP.get(error_code)
                    error = 'ERROR: %s\n\n%s: %s' % (error_str, error_code,
                                                     error_msg)
                    _logger.info(error)
                    raise Exception(error)

            tries = tries - 1

        if not tx_done and tries == 0:
            raise Exception('Cannot get transaction status...')

        return tx_status
예제 #20
0
class product_template(osv.Model):
    _inherit = [
        "product.template", "website.seo.metadata", 'website.published.mixin',
        'rating.mixin'
    ]
    _order = 'website_published desc, website_sequence desc, name'
    _name = 'product.template'
    _mail_post_access = 'read'

    def _website_url(self, cr, uid, ids, field_name, arg, context=None):
        res = super(product_template, self)._website_url(cr,
                                                         uid,
                                                         ids,
                                                         field_name,
                                                         arg,
                                                         context=context)
        for product in self.browse(cr, uid, ids, context=context):
            res[product.id] = "/shop/product/%s" % (product.id, )
        return res

    _columns = {
        # TODO FIXME tde: when website_mail/mail_thread.py inheritance work -> this field won't be necessary
        'website_message_ids':
        fields.one2many(
            'mail.message',
            'res_id',
            domain=lambda self: [
                '&', ('model', '=', self._name),
                ('message_type', '=', 'comment')
            ],
            string='Website Comments',
        ),
        'website_description':
        fields.html('Description for the website',
                    sanitize=False,
                    translate=True),
        'alternative_product_ids':
        fields.many2many('product.template',
                         'product_alternative_rel',
                         'src_id',
                         'dest_id',
                         string='Suggested Products',
                         help='Appear on the product page'),
        'accessory_product_ids':
        fields.many2many('product.product',
                         'product_accessory_rel',
                         'src_id',
                         'dest_id',
                         string='Accessory Products',
                         help='Appear on the shopping cart'),
        'website_size_x':
        fields.integer('Size X'),
        'website_size_y':
        fields.integer('Size Y'),
        'website_style_ids':
        fields.many2many('product.style', string='Styles'),
        'website_sequence':
        fields.integer(
            'Sequence',
            help="Determine the display order in the Website E-commerce"),
        'public_categ_ids':
        fields.many2many(
            'product.public.category',
            string='Website Product Category',
            help=
            "Those categories are used to group similar products for e-commerce."
        ),
    }

    def _defaults_website_sequence(self, cr, uid, *l, **kwargs):
        cr.execute('SELECT MIN(website_sequence)-1 FROM product_template')
        next_sequence = cr.fetchone()[0] or 10
        return next_sequence

    _defaults = {
        'website_size_x': 1,
        'website_size_y': 1,
        'website_sequence': _defaults_website_sequence,
    }

    def set_sequence_top(self, cr, uid, ids, context=None):
        cr.execute('SELECT MAX(website_sequence) FROM product_template')
        max_sequence = cr.fetchone()[0] or 0
        return self.write(cr,
                          uid,
                          ids, {'website_sequence': max_sequence + 1},
                          context=context)

    def set_sequence_bottom(self, cr, uid, ids, context=None):
        cr.execute('SELECT MIN(website_sequence) FROM product_template')
        min_sequence = cr.fetchone()[0] or 0
        return self.write(cr,
                          uid,
                          ids, {'website_sequence': min_sequence - 1},
                          context=context)

    def set_sequence_up(self, cr, uid, ids, context=None):
        product = self.browse(cr, uid, ids[0], context=context)
        cr.execute("""  SELECT id, website_sequence FROM product_template
                        WHERE website_sequence > %s AND website_published = %s ORDER BY website_sequence ASC LIMIT 1"""
                   % (product.website_sequence, product.website_published))
        prev = cr.fetchone()
        if prev:
            self.write(cr,
                       uid, [prev[0]],
                       {'website_sequence': product.website_sequence},
                       context=context)
            return self.write(cr,
                              uid, [ids[0]], {'website_sequence': prev[1]},
                              context=context)
        else:
            return self.set_sequence_top(cr, uid, ids, context=context)

    def set_sequence_down(self, cr, uid, ids, context=None):
        product = self.browse(cr, uid, ids[0], context=context)
        cr.execute("""  SELECT id, website_sequence FROM product_template
                        WHERE website_sequence < %s AND website_published = %s ORDER BY website_sequence DESC LIMIT 1"""
                   % (product.website_sequence, product.website_published))
        next = cr.fetchone()
        if next:
            self.write(cr,
                       uid, [next[0]],
                       {'website_sequence': product.website_sequence},
                       context=context)
            return self.write(cr,
                              uid, [ids[0]], {'website_sequence': next[1]},
                              context=context)
        else:
            return self.set_sequence_bottom(cr, uid, ids, context=context)
예제 #21
0
파일: web_note.py 프로젝트: sunshineLhj/web
class WebNote(orm.Model):
    _name = 'web.note'

    def name_get(self, cr, uid, ids, context=None):
        if context is None:
            context = {}
        if isinstance(ids, (int, long)):
            ids = [ids]
        res = []
        for record in self.browse(cr, uid, ids, context=context):
            name = remove_extra_spaces(remove_html_tags(record.message or ''))

            res.append((record.id, name))
        return res

    def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
        res = self.name_get(cr, uid, ids, context=context)
        return dict(res)

    def type_selection(self, cr, uid, context=None):
        return (('private', 'private'), ('internal', 'internal'), ('external',
                                                                   'external'))

    def onchange_container_id(self,
                              cr,
                              uid,
                              ids,
                              container_id=False,
                              message=None,
                              context=None):
        result = {}
        if container_id:
            container = self.pool.get('web.note.container').\
                browse(cr, uid, container_id, context=context)
            result['value'] = {
                'sequence':
                container.sequence or self._defaults.get('sequence', 0),
            }
            if container.pattern:
                result['value']['message'] = container.pattern
        return result

    _order = 'sequence,create_date'

    _columns = {
        'type':
        fields.selection(_type_selection, 'Note type', required=True),
        'message':
        fields.html('Message'),
        'display_name':
        fields.function(_name_get_fnc, type="char", string='Note',
                        store=False),
        'container_id':
        fields.many2one('web.note.container',
                        'Note-Container',
                        help='Containers include '
                        'templates for order and heading.'),
        'sequence':
        fields.integer('Order in report'),
        'create_uid':
        fields.many2one('res.users', 'User', readonly=True),
    }

    _defaults = {
        'sequence': 10,
    }
예제 #22
0
class sale_order_option(osv.osv):
    _name = "sale.order.option"
    _description = "Sale Options"
    _order = 'sequence, id'
    _columns = {
        'order_id':
        fields.many2one('sale.order',
                        'Sale Order Reference',
                        ondelete='cascade',
                        select=True),
        'line_id':
        fields.many2one('sale.order.line', on_delete="set null"),
        'name':
        fields.text('Description', required=True),
        'product_id':
        fields.many2one('product.product',
                        'Product',
                        domain=[('sale_ok', '=', True)]),
        'layout_category_id':
        fields.many2one('sale.layout_category', string='Section'),
        'website_description':
        fields.html('Line Description'),
        'price_unit':
        fields.float('Unit Price',
                     required=True,
                     digits_compute=dp.get_precision('Product Price')),
        'discount':
        fields.float('Discount (%)',
                     digits_compute=dp.get_precision('Discount')),
        'uom_id':
        fields.many2one('product.uom', 'Unit of Measure ', required=True),
        'quantity':
        fields.float('Quantity',
                     required=True,
                     digits_compute=dp.get_precision('Product UoS')),
        'sequence':
        fields.integer(
            'Sequence',
            help=
            "Gives the sequence order when displaying a list of suggested product."
        ),
    }

    _defaults = {
        'quantity': 1,
    }

    # TODO master: to remove, replaced by onchange of the new api
    def on_change_product_id(self,
                             cr,
                             uid,
                             ids,
                             product,
                             uom_id=None,
                             context=None):
        vals, domain = {}, []
        if not product:
            return vals
        product_obj = self.pool.get('product.product').browse(cr,
                                                              uid,
                                                              product,
                                                              context=context)
        name = product_obj.name
        if product_obj.description_sale:
            name += '\n' + product_obj.description_sale
        vals.update({
            'price_unit':
            product_obj.list_price,
            'website_description':
            product_obj and
            (product_obj.quote_description or product_obj.website_description),
            'name':
            name,
            'uom_id':
            uom_id or product_obj.uom_id.id,
        })
        uom_obj = self.pool.get('product.uom')
        if vals['uom_id'] != product_obj.uom_id.id:
            selected_uom = uom_obj.browse(cr,
                                          uid,
                                          vals['uom_id'],
                                          context=context)
            new_price = uom_obj._compute_price(cr, uid, product_obj.uom_id.id,
                                               vals['price_unit'],
                                               vals['uom_id'])
            vals['price_unit'] = new_price
        if not uom_id:
            domain = {
                'uom_id':
                [('category_id', '=', product_obj.uom_id.category_id.id)]
            }
        return {'value': vals, 'domain': domain}

    # TODO master: to remove, replaced by onchange of the new api
    def product_uom_change(self, cr, uid, ids, product, uom_id, context=None):
        context = context or {}
        if not uom_id:
            return {'value': {'price_unit': 0.0, 'uom_id': False}}
        return self.on_change_product_id(cr,
                                         uid,
                                         ids,
                                         product,
                                         uom_id=uom_id,
                                         context=context)

    @api.onchange('product_id', 'uom_id')
    def _onchange_product_id(self):
        if not self.product_id:
            return
        product = self.product_id.with_context(
            lang=self.order_id.partner_id.lang)
        self.price_unit = product.list_price
        self.website_description = product.quote_description or product.website_description
        self.name = product.name
        if product.description_sale:
            self.name += '\n' + product.description_sale
        self.uom_id = self.uom_id or product.uom_id
        pricelist = self.order_id.pricelist_id
        if pricelist and product:
            partner_id = self.order_id.partner_id.id
            self.price_unit = pricelist.with_context(
                uom=self.uom_id.id).price_get(product.id, self.quantity,
                                              partner_id)[pricelist.id]
        domain = {
            'uom_id':
            [('category_id', '=', self.product_id.uom_id.category_id.id)]
        }
        return {'domain': domain}

    @api.multi
    def button_add_to_order(self):
        order = self.order_id
        if order.state not in ['draft', 'sent']:
            return False
        option = self

        if option.product_id in [line.product_id for line in order.order_line]:
            line = [
                line for line in order.order_line
                if line.product_id == option.product_id
            ][0]
            vals = {
                'product_uom_qty': line.product_uom_qty + 1,
            }
            line.write(vals)
        else:
            vals = {
                'price_unit': option.price_unit,
                'website_description': option.website_description,
                'name': option.name,
                'order_id': order.id,
                'product_id': option.product_id.id,
                'layout_category_id': option.layout_category_id.id,
                'product_uom_qty': option.quantity,
                'product_uom': option.uom_id.id,
                'discount': option.discount,
            }
            line = self.env['sale.order.line'].create(vals)

        self.env['sale.order.line']._compute_tax_id()
        self.env['sale.order.option'].write({'line_id': line})
        return {'type': 'ir.actions.client', 'tag': 'reload'}
예제 #23
0
class employee_requisition(osv.Model):
    _name = 'employee.requisition'
    _description = 'Employee Requisition'
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _track = {
        'state': {
            'hr_holidays.mt_holidays_approved':
            lambda self, cr, uid, obj, ctx=None: obj['state'] == 'validate',
            'hr_holidays.mt_holidays_refused':
            lambda self, cr, uid, obj, ctx=None: obj['state'] == 'refuse',
            'hr_holidays.mt_holidays_confirmed':
            lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirm',
        },
    }

    def _employee_get(self, cr, uid, context=None):
        ids = self.pool.get('hr.employee').search(cr,
                                                  uid, [('user_id', '=', uid)],
                                                  context=context)
        if ids:
            # print "My ID.........",ids
            print self.pool.get('hr.employee').browse(cr, uid,
                                                      ids[0]).parent_id.name
            print self.pool.get('hr.employee').browse(
                cr, uid, ids[0]).parent_id.user_id.id
            return ids[0]
        return False

    def _deaprtment_get(self, cr, uid, context=None):
        ids = self.pool.get('hr.employee').search(cr,
                                                  uid, [('user_id', '=', uid)],
                                                  context=context)
        if ids:
            department_id = self.pool.get('hr.employee').browse(
                cr, uid, ids).department_id
            return department_id.id
        return False

    def create(self, cr, uid, vals, context=None):
        vals['state'] = 'confirm'
        if 'created_by' in vals and vals['created_by']:
            emp_id = self.pool.get('hr.employee').browse(
                cr, uid, vals['created_by'])
            if emp_id.parent_id and emp_id.parent_id.user_id.id:
                vals['approve_user_id'] = emp_id.parent_id.user_id.id
                vals['user_id'] = emp_id.user_id and emp_id.user_id.id or False
        res = super(employee_requisition, self).create(cr,
                                                       uid,
                                                       vals,
                                                       context=None)
        return res

    def set_recruit(self, cr, uid, ids, context=None):
        res_list = []
        for job in self.browse(cr, uid, ids, context=context).job_line:
            res_list.append({'job_id': job.job_id.id, 'vacancy': job.vacancy})
        job_ids = self.pool.get('hr.job').search(cr, uid, [])
        for each in self.pool.get('hr.job').browse(cr, uid, job_ids):
            if each.no_of_recruitment > 0:
                res_list.append({
                    'job_id': each.id,
                    'vacancy': each.no_of_recruitment
                })
        for key, group in itertools.groupby(sorted(res_list),
                                            lambda item: item["job_id"]):
            no_of_recruitment = sum([item["vacancy"] for item in group])
            self.pool.get('hr.job').write(
                cr,
                uid, [key], {
                    'state': 'recruit',
                    'no_of_recruitment': no_of_recruitment
                },
                context=context)
        res = self.write(cr, uid, ids, {'state': 'recruit'}, context=context)
        return res

    def set_recruit_close(self, cr, uid, ids, context=None):
        for job in self.browse(cr, uid, ids, context=context).job_line:
            no_of_recruitment = job.vacancy == 0 and 1 or job.vacancy
            self.pool.get('hr.job').write(cr,
                                          uid, [job.job_id.id], {
                                              'state': 'open',
                                              'no_of_recruitment': 0
                                          },
                                          context=context)
            res = self.write(cr, uid, ids, {'state': 'open'}, context=context)
        return res

    _columns = {
        'name':
        fields.char('Ref Number'),
        'department_id':
        fields.many2one('hr.department', 'Department Name'),
        'address_id':
        fields.many2many('res.partner', 'res_partner_requision_rel',
                         'requision_id', 'requision_address_id', 'Location'),
        'job_line':
        fields.one2many('requision.job.line', 'requision_id', 'Position'),
        'date_request':
        fields.date('Date Request'),
        'created_by':
        fields.many2one('hr.employee', 'Created By'),
        'reporting_authority':
        fields.many2one('hr.employee', 'Reporting Authority'),
        'job_description':
        fields.html('Job Description'),
        'primary_responsibilities':
        fields.html('Responsibilities'),
        'preferred_indust':
        fields.char('Preferred Industry'),
        'type_ids':
        fields.many2many('hr.recruitment.degree', 'education_group_rel',
                         'requision_id', 'education_master_id',
                         'Education Qualification'),
        'state':
        fields.selection(
            [('draft', 'To Submit'), ('confirm', 'To Approve'),
             ('refuse', 'Refused'), ('validate1', 'Approval'),
             ('validate', 'Approved'), ('recruit', 'Recruitment in Progress'),
             ('open', 'Recruitment Closed')],
            'Status',
            track_visibility='onchange',
            help=
            'The status is set to \'To Submit\', when a holiday request is created.\
                 \nThe status is \'To Approve\', when holiday request is confirmed by user.\
                 \nThe status is \'Refused\', when holiday request is refused by manager.\
                 \nThe status is \'Approved\', when holiday request is approved by manager.'
        ),
        'user_id':
        fields.many2one('res.users', 'User'),
        'approve_user_id':
        fields.many2one('res.users', 'Approve User')
    }
    _defaults = {
        'state': 'draft',
        'department_id': _deaprtment_get,
        'date_request': fields.date.context_today,
        'user_id': lambda obj, cr, uid, context: uid,
        'created_by': _employee_get,
    }

    def approve_requision_req(self, cr, uid, ids, context=None):
        res = self.write(cr, uid, ids, {'state': 'validate'}, context=None)
        return res

    def refuse_requision_req(self, cr, uid, ids, context=None):
        res = self.write(cr, uid, ids, {'state': 'refuse'}, context=None)
        return res

    def reset_to_approve(self, cr, uid, ids, context=None):
        res = self.write(cr, uid, ids, {'state': 'confirm'}, context=None)
        return res
class ProtocolloMailPecWizard(osv.TransientModel):
    """
        A wizard to manage the creation of
        document protocollo from mail or pec message
    """
    _name = 'protocollo.mailpec.wizard'
    _description = 'Create Protocollo From Mail or PEC'
    _rec_name = 'subject'

    def _get_doc_principale_option(self, cr, uid, context=None):
        options = []
        attach_lower_limit = 0
        configurazione_ids = self.pool.get('protocollo.configurazione').search(
            cr, uid, [])
        configurazione = self.pool.get('protocollo.configurazione').browse(
            cr, uid, configurazione_ids[0])
        message = None
        if context and context.has_key('active_id') and context['active_id']:
            message = self.pool.get('mail.message').browse(
                cr, uid, context['active_id'])

        if configurazione.select_body:
            options.append(('testo', 'Corpo del messaggio'))

        if message and message.eml and configurazione.select_eml:
            options.append(('eml', 'Intero messaggio (file EML)'))
            attach_lower_limit = 1  #al momento le PEC si includono anche l'attachment EML quindi il controllo parte da 1

        if 'attachment_ids' in context and len(
                context['attachment_ids'][0]
            [2]) > attach_lower_limit and configurazione.select_attachments:
            options.insert(0, ('allegato', 'Allegato'))

        return options

    def on_change_attachment(self, cr, uid, ids, attachment_id, context=None):
        values = {'preview': False}
        if attachment_id:
            ir_attachment = self.pool.get('ir.attachment').browse(
                cr, uid, attachment_id)
            for attach in ir_attachment:
                if attach.file_type == 'application/pdf':
                    values = {
                        'preview': attach.datas,
                    }
                return {'value': values}
        else:
            return None

    _columns = {
        'registration_employee_department_id':
        fields.many2one('hr.department', 'Il mio ufficio'),
        'registration_employee_department_id_invisible':
        fields.boolean('Campo registration_employee_department_id invisible',
                       readonly=True),
        'subject':
        fields.text('Oggetto', readonly=True),
        'body':
        fields.html('Corpo della mail', readonly=True),
        'receiving_date':
        fields.datetime('Data Ricezione', required=False, readonly=True),
        'message_id':
        fields.integer('Id', required=True, readonly=True),
        'select_doc_principale':
        fields.selection(_get_doc_principale_option,
                         'Seleziona il documento da protocollare',
                         select=True,
                         required=True),
        'doc_principale':
        fields.many2one('ir.attachment',
                        'Allegato',
                        domain="[('datas_fname', '=', 'original_email.eml')]"),
        'is_attach_message':
        fields.related('ir.attachment',
                       'doc_principale',
                       type='boolean',
                       string="Author's Avatar"),
        'doc_fname':
        fields.related('doc_principale',
                       'datas_fname',
                       type='char',
                       readonly=True),
        'doc_description':
        fields.char('Descrizione documento', size=256, readonly=False),
        'preview':
        fields.binary('Anteprima allegato PDF'),
        'sender_receivers':
        fields.one2many('protocollo.sender_receiver.wizard',
                        'wizard_id',
                        'Mittenti/Destinatari',
                        required=True,
                        limit=1),
        'documento_descrizione_required_wizard':
        fields.boolean('Descrizione documento obbligatorio', readonly=1)
        # 'dossier_ids': fields.many2many(
        #     'protocollo.dossier',
        #     'dossier_protocollo_pec_rel',
        #     'wizard_id', 'dossier_id',
        #     'Fascicoli'),
        # TODO: insert assigne here
        # 'notes': fields.text('Note'),
    }

    # def _default_doc_principale(self, cr, uid, context):
    #     id = 0
    #     mail_message = self.pool.get('mail.message').browse(cr, uid, context['active_id'], context=context)
    #     for attach in mail_message.attachment_ids:
    #         if attach.name == 'original_email.eml':
    #             id = attach.id
    #     return id

    def _default_registration_employee_department_id(self, cr, uid, context):
        department_ids = self.pool.get('hr.department').search(
            cr, uid, [('can_used_to_protocol', '=', True)])
        if department_ids:
            return department_ids[0]
        return False

    def _default_registration_employee_department_id_invisible(
            self, cr, uid, context):
        department_ids = self.pool.get('hr.department').search(
            cr, uid, [('can_used_to_protocol', '=', True)])
        if len(department_ids) == 1:
            return True
        return False

    def _default_subject(self, cr, uid, context):
        mail_message = self.pool.get('mail.message').browse(
            cr, uid, context['active_id'], context=context)
        return mail_message.subject

    def _default_id(self, cr, uid, context):
        mail_message = self.pool.get('mail.message').browse(
            cr, uid, context['active_id'], context=context)
        return mail_message.id

    def _default_receiving_date(self, cr, uid, context):
        mail_message = self.pool.get('mail.message').browse(
            cr, uid, context['active_id'], context=context)
        if mail_message.server_received_datetime:
            return mail_message.server_received_datetime
        return mail_message.date

    def _default_body(self, cr, uid, context):
        mail_message = self.pool.get('mail.message').browse(
            cr, uid, context['active_id'], context=context)
        return mail_message.body

    def _default_sender_receivers(self, cr, uid, context):
        mail_message = self.pool.get('mail.message').browse(
            cr, uid, context['active_id'], context=context)
        partner = mail_message.author_id
        res = []
        sr_substring = re.findall('<[^>]+>', mail_message.email_from)
        if len(sr_substring):
            sr_email = sr_substring[0].strip('<>')
            sr_name = mail_message.email_from.replace(sr_substring[0],
                                                      '').replace('"',
                                                                  '').strip()
        else:
            sr_name = ''
            sr_email = mail_message.email_from

        if partner:
            res.append({
                'partner_id': partner.id,
                'type': partner.is_company and 'legal' or 'individual',
                'name': partner.name,
                'street': partner.street,
                'zip': partner.zip,
                'city': partner.city,
                'country_id': partner.country_id.id,
                'email': partner.email,
                'phone': partner.phone,
                'fax': partner.fax,
                'mobile': partner.mobile,
                'pec_mail': partner.pec_mail
            })
        elif 'message_type' in context and context['message_type'] == 'mail':
            res.append({
                'name': sr_name,
                'email': sr_email,
                'type': 'individual',
            })
        elif 'message_type' in context and context['message_type'] == 'pec':
            res.append({
                'name': sr_name,
                'pec_mail': sr_email,
                'type': 'individual',
            })
        return res

    def _default_documento_descrizione_wizard_required(self, cr, uid, context):
        configurazione_ids = self.pool.get('protocollo.configurazione').search(
            cr, uid, [])
        configurazione = self.pool.get('protocollo.configurazione').browse(
            cr, uid, configurazione_ids[0])
        return configurazione.documento_descrizione_required

    _defaults = {
        'registration_employee_department_id':
        _default_registration_employee_department_id,
        'registration_employee_department_id_invisible':
        _default_registration_employee_department_id_invisible,
        'subject':
        _default_subject,
        'message_id':
        _default_id,
        'receiving_date':
        _default_receiving_date,
        'body':
        _default_body,
        'sender_receivers':
        _default_sender_receivers,
        'documento_descrizione_required_wizard':
        _default_documento_descrizione_wizard_required
        # 'doc_principale': _default_doc_principale,
    }

    def action_save(self, cr, uid, ids, context=None):
        wizard = self.browse(cr, uid, ids[0], context=context)
        protocollo_obj = self.pool.get('protocollo.protocollo')
        sender_receiver_obj = self.pool.get('protocollo.sender_receiver')
        protocollo_typology_obj = self.pool.get('protocollo.typology')
        mail_message_obj = self.pool.get('mail.message')
        mail_message = mail_message_obj.browse(cr,
                                               uid,
                                               context['active_id'],
                                               context=context)
        employee = self.pool.get('hr.employee').get_department_employee(
            cr, uid, wizard.registration_employee_department_id.id)

        vals = {}
        vals['type'] = 'in'
        vals['receiving_date'] = wizard.receiving_date
        vals[
            'subject'] = wizard.subject if wizard.select_doc_principale == 'eml' else ''
        vals['body'] = wizard.body
        vals['mail_pec_ref'] = context['active_id']
        vals['user_id'] = uid
        vals[
            'registration_employee_department_id'] = wizard.registration_employee_department_id.id
        vals[
            'registration_employee_department_name'] = wizard.registration_employee_department_id.complete_name
        vals['registration_employee_id'] = employee.id
        vals['registration_employee_name'] = employee.name_related
        sender_receiver = []

        is_pec = False
        is_segnatura = False

        # Estrae i dati del mittente dalla segnatura
        configurazione_ids = self.pool.get('protocollo.configurazione').search(
            cr, uid, [])
        configurazione = self.pool.get('protocollo.configurazione').browse(
            cr, uid, configurazione_ids[0])

        if 'message_type' in context and context['message_type'] == 'pec':
            is_pec = True

        if is_pec:
            srvals = {}
            typology_id = protocollo_typology_obj.search(
                cr, uid, [('pec', '=', True)])[0]
            messaggio_pec_obj = self.pool.get('protocollo.messaggio.pec')
            messaggio_pec_id = messaggio_pec_obj.create(
                cr, uid, {
                    'type': 'messaggio',
                    'messaggio_ref': mail_message.id
                })

            if configurazione.segnatura_xml_parse:
                srvals = self.elaboraSegnatura(cr, uid, protocollo_obj,
                                               mail_message)
            if len(srvals) > 0 and len(srvals['mittente']) > 0:
                is_segnatura = True

        sender_segnatura_xml_parse = configurazione.sender_segnatura_xml_parse

        if is_pec and is_segnatura:
            srvals['mittente']['pec_messaggio_ids'] = [[
                6, 0, [messaggio_pec_id]
            ]]
            if sender_segnatura_xml_parse:
                sender_receiver.append(
                    sender_receiver_obj.create(cr, uid, srvals['mittente']))

        if (is_pec and (is_segnatura is False or
                        (is_segnatura and not sender_segnatura_xml_parse))
            ) or is_pec is False:
            for send_rec in wizard.sender_receivers:
                srvals = {
                    'type':
                    send_rec.type,
                    'source':
                    'sender',
                    'partner_id':
                    send_rec.partner_id and send_rec.partner_id.id or False,
                    'name':
                    send_rec.name,
                    'street':
                    send_rec.street,
                    'zip':
                    send_rec.zip,
                    'city':
                    send_rec.city,
                    'country_id':
                    send_rec.country_id and send_rec.country_id.id or False,
                    'phone':
                    send_rec.phone,
                    'fax':
                    send_rec.fax,
                    'mobile':
                    send_rec.mobile,
                }

                if is_pec:
                    srvals['pec_mail'] = send_rec.pec_mail
                    srvals['pec_messaggio_ids'] = [[6, 0, [messaggio_pec_id]]]
                else:
                    srvals['pec_mail'] = ''
                    srvals['email'] = send_rec.email
                    srvals['sharedmail_messaggio_ids'] = [
                        (4, context['active_id'])
                    ]

                sender_receiver.append(
                    sender_receiver_obj.create(cr, uid, srvals))

        vals['sender_receivers'] = [[6, 0, sender_receiver]]
        if 'protocollo' in srvals:
            vals['sender_protocol'] = srvals['protocollo']['sender_protocol']
            vals['sender_register'] = srvals['protocollo']['sender_register']
            vals['sender_registration_date'] = srvals['protocollo'][
                'sender_registration_date']

        if is_pec is False:
            typology_id = protocollo_typology_obj.search(
                cr, uid, [('sharedmail', '=', True)])[0]

        vals['typology'] = typology_id
        protocollo_id = protocollo_obj.create(cr, uid, vals)

        if is_pec:
            self.pool.get('mail.message').write(
                cr,
                SUPERUSER_ID,
                context['active_id'], {'pec_protocol_ref': protocollo_id},
                context=context)
        else:
            self.pool.get('mail.message').write(
                cr,
                SUPERUSER_ID,
                context['active_id'],
                {'sharedmail_protocol_ref': protocollo_id},
                context=context)

        action_class = "history_icon print"
        post_vars = {
            'subject':
            "Creata Bozza Protocollo",
            'body':
            "<div class='%s'><ul><li>Messaggio convertito in bozza di protocollo</li></ul></div>"
            % action_class,
            'model':
            "protocollo.protocollo",
            'res_id':
            context['active_id'],
        }

        thread_pool = self.pool.get('protocollo.protocollo')
        thread_pool.message_post(cr,
                                 uid,
                                 protocollo_id,
                                 type="notification",
                                 context=context,
                                 **post_vars)

        # Attachments
        file_data_list = []

        body_pdf_content = base64.b64encode(
            ConversionUtility.html_to_pdf(wizard.body))
        body_pdf_name = "mailbody.pdf"

        if wizard.select_doc_principale == 'testo':
            protocollo_obj.carica_documento_principale(cr, uid, protocollo_id,
                                                       body_pdf_content,
                                                       body_pdf_name,
                                                       wizard.doc_description,
                                                       {'skip_check': True})
        else:
            file_data_list.append({
                'datas': body_pdf_content,
                'datas_fname': body_pdf_name,
                'datas_description': ''
            })

        for attach in mail_message.attachment_ids:
            if attach.name == 'original_email.eml':
                if wizard.select_doc_principale == 'eml':
                    protocollo_obj.carica_documento_principale(
                        cr, uid, protocollo_id, attach.datas, attach.name,
                        wizard.doc_description, {'skip_check': True})
            else:
                if wizard.select_doc_principale == 'allegato' and attach.id == wizard.doc_principale.id:
                    if attach.datas and attach.name:
                        protocollo_obj.carica_documento_principale(
                            cr, uid, protocollo_id, attach.datas, attach.name,
                            wizard.doc_description, {'skip_check': True})
                else:
                    file_data_list.append({
                        'datas': attach.datas,
                        'datas_fname': attach.name,
                        'datas_description': ''
                    })

        if file_data_list:
            protocollo_obj.carica_documenti_secondari(cr, uid, protocollo_id,
                                                      file_data_list)

        obj_model = self.pool.get('ir.model.data')
        model_data_ids = obj_model.search(
            cr, uid, [('model', '=', 'ir.ui.view'),
                      ('name', '=', 'protocollo_protocollo_form')])
        resource_id = obj_model.read(cr,
                                     uid,
                                     model_data_ids,
                                     fields=['res_id'])[0]['res_id']

        return {
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'protocollo.protocollo',
            'res_id': protocollo_id,
            'views': [(resource_id, 'form')],
            'type': 'ir.actions.act_window',
            'context': context,
            'flags': {
                'initial_mode': 'edit'
            }
        }

    def elaboraSegnatura(self, cr, uid, protocollo_obj, mail_message):
        srvals = {}
        srvals_mittente = {}
        srvals_protocollo = {}

        for attach in mail_message.attachment_ids:
            if attach.name.lower() == 'segnatura.xml' and attach.datas:
                attach_path = self.pool.get('ir.attachment')._full_path(
                    cr, uid, attach.store_fname)
                xml = open(attach_path, "rb").read()
                content_encode = xml.decode("latin").encode("utf8")
                tree = etree.fromstring(content_encode)
                segnatura_xml = SegnaturaXMLParser(tree)

                srvals_mittente = self.getDatiSegnaturaMittente(segnatura_xml)
                srvals_protocollo = self.getDatiSegnaturaProtocollo(
                    segnatura_xml)

                srvals_mittente['pec_mail'] = mail_message.email_from.encode(
                    'utf8')

        srvals['mittente'] = srvals_mittente
        srvals['protocollo'] = srvals_protocollo
        return srvals

    def getDatiSegnaturaMittente(self, segnatura_xml):
        srvals = {
            'type': segnatura_xml.getTipoMittente(),
            'pa_type': segnatura_xml.getTipoAmministrazione(),
            'source': 'sender',
            'partner_id': False,
            'name': segnatura_xml.getDenominazioneCompleta(),
            'street': segnatura_xml.getToponimo(),
            'zip': segnatura_xml.getCAP(),
            'city': segnatura_xml.getComune(),
            'country_id': False,
            'email': segnatura_xml.getIndirizzoTelematico(),
            'phone': segnatura_xml.getTelefono(),
            'fax': segnatura_xml.getFax(),
            'ipa_code': segnatura_xml.getCodiceUnitaOrganizzativa(),
            'ident_code': segnatura_xml.getCodiceAOO(),
            'amm_code': segnatura_xml.getCodiceAmministrazione()
        }

        return srvals

    def getDatiSegnaturaProtocollo(self, segnatura_xml):
        srvals = {
            'sender_protocol': segnatura_xml.getNumeroRegistrazione(),
            'sender_register': segnatura_xml.getCodiceRegistro(),
            'sender_registration_date': segnatura_xml.getDataRegistrazione()
        }

        return srvals
예제 #25
0
class MassMailing(osv.Model):
    """ MassMailing models a wave of emails for a mass mailign campaign.
    A mass mailing is an occurence of sending emails. """

    _name = 'mail.mass_mailing'
    _description = 'Mass Mailing'
    # number of periods for tracking mail_mail statistics
    _period_number = 6
    _order = 'sent_date DESC'
    # _send_trigger = 5  # Number under which mails are send directly

    _inherit = ['utm.mixin']

    def _get_statistics(self, cr, uid, ids, name, arg, context=None):
        """ Compute statistics of the mass mailing """
        results = {}
        cr.execute(
            """
            SELECT
                m.id as mailing_id,
                COUNT(s.id) AS total,
                COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent,
                COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null THEN 1 ELSE null END) AS scheduled,
                COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is not null THEN 1 ELSE null END) AS failed,
                COUNT(CASE WHEN s.sent is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered,
                COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened,
                COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied,
                COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced,
                COUNT(CASE WHEN s.exception is not null THEN 1 ELSE null END) AS failed
            FROM
                mail_mail_statistics s
            RIGHT JOIN
                mail_mass_mailing m
                ON (m.id = s.mass_mailing_id)
            WHERE
                m.id IN %s
            GROUP BY
                m.id
        """, (tuple(ids), ))
        for row in cr.dictfetchall():
            results[row.pop('mailing_id')] = row
            total = row['total'] or 1
            row['received_ratio'] = 100.0 * row['delivered'] / total
            row['opened_ratio'] = 100.0 * row['opened'] / total
            row['replied_ratio'] = 100.0 * row['replied'] / total
            row['bounced_ratio'] = 100.0 * row['bounced'] / total
        return results

    def _get_mailing_model(self, cr, uid, context=None):
        res = []
        for model_name in self.pool:
            model = self.pool[model_name]
            if hasattr(model, '_mail_mass_mailing') and getattr(
                    model, '_mail_mass_mailing'):
                if getattr(model, 'message_mass_mailing_enabled'):
                    res.append(
                        (model._name, model.message_mass_mailing_enabled()))
                else:
                    res.append((model._name, model._mail_mass_mailing))
        res.append(('mail.mass_mailing.contact', _('Mailing List')))
        return res

    def _get_clicks_ratio(self, cr, uid, ids, name, arg, context=None):
        res = dict.fromkeys(ids, 0)
        cr.execute(
            """
            SELECT COUNT(DISTINCT(stats.id)) AS nb_mails, COUNT(DISTINCT(clicks.mail_stat_id)) AS nb_clicks, stats.mass_mailing_id AS id 
            FROM mail_mail_statistics AS stats
            LEFT OUTER JOIN link_tracker_click AS clicks ON clicks.mail_stat_id = stats.id
            WHERE stats.mass_mailing_id IN %s
            GROUP BY stats.mass_mailing_id
        """, (tuple(ids), ))

        for record in cr.dictfetchall():
            res[record['id']] = 100 * record['nb_clicks'] / record['nb_mails']

        return res

    def _get_next_departure(self, cr, uid, ids, name, arg, context=None):
        mass_mailings = self.browse(cr, uid, ids, context=context)
        cron_next_call = self.pool.get('ir.model.data').xmlid_to_object(
            cr,
            SUPERUSER_ID,
            'mass_mailing.ir_cron_mass_mailing_queue',
            context=context).nextcall

        result = {}
        for mass_mailing in mass_mailings:
            schedule_date = mass_mailing.schedule_date
            if schedule_date:
                if datetime.now() > datetime.strptime(
                        schedule_date, tools.DEFAULT_SERVER_DATETIME_FORMAT):
                    result[mass_mailing.id] = cron_next_call
                else:
                    result[mass_mailing.id] = schedule_date
            else:
                result[mass_mailing.id] = cron_next_call
        return result

    def _get_total(self, cr, uid, ids, name, arg, context=None):
        mass_mailings = self.browse(cr, uid, ids, context=context)

        result = {}
        for mass_mailing in mass_mailings:
            mailing = self.browse(cr, uid, mass_mailing.id, context=context)
            result[mass_mailing.id] = len(
                self.get_recipients(cr, SUPERUSER_ID, mailing,
                                    context=context))
        return result

    # indirections for inheritance
    _mailing_model = lambda self, *args, **kwargs: self._get_mailing_model(
        *args, **kwargs)

    _columns = {
        'name':
        fields.char('Subject', required=True),
        'active':
        fields.boolean('Active'),
        'email_from':
        fields.char('From', required=True),
        'create_date':
        fields.datetime('Creation Date'),
        'sent_date':
        fields.datetime('Sent Date', oldname='date', copy=False),
        'schedule_date':
        fields.datetime('Schedule in the Future'),
        'body_html':
        fields.html('Body', translate=True),
        'attachment_ids':
        fields.many2many('ir.attachment', 'mass_mailing_ir_attachments_rel',
                         'mass_mailing_id', 'attachment_id', 'Attachments'),
        'keep_archives':
        fields.boolean('Keep Archives'),
        'mass_mailing_campaign_id':
        fields.many2one(
            'mail.mass_mailing.campaign',
            'Mass Mailing Campaign',
            ondelete='set null',
        ),
        'clicks_ratio':
        fields.function(
            _get_clicks_ratio,
            string="Number of Clicks",
            type="integer",
        ),
        'state':
        fields.selection([('draft', 'Draft'), ('in_queue', 'In Queue'),
                          ('sending', 'Sending'), ('done', 'Sent')],
                         string='Status',
                         required=True,
                         copy=False),
        'color':
        fields.related(
            'mass_mailing_campaign_id',
            'color',
            type='integer',
            string='Color Index',
        ),
        # mailing options
        'reply_to_mode':
        fields.selection(
            [('thread', 'Followers of leads/applicants'),
             ('email', 'Specified Email Address')],
            string='Reply-To Mode',
            required=True,
        ),
        'reply_to':
        fields.char('Reply To', help='Preferred Reply-To Address'),
        # recipients
        'mailing_model':
        fields.selection(_mailing_model,
                         string='Recipients Model',
                         required=True),
        'mailing_domain':
        fields.char('Domain', oldname='domain'),
        'contact_list_ids':
        fields.many2many(
            'mail.mass_mailing.list',
            'mail_mass_mailing_list_rel',
            string='Mailing Lists',
        ),
        'contact_ab_pc':
        fields.integer(
            'A/B Testing percentage',
            help=
            'Percentage of the contacts that will be mailed. Recipients will be taken randomly.'
        ),
        # statistics data
        'statistics_ids':
        fields.one2many(
            'mail.mail.statistics',
            'mass_mailing_id',
            'Emails Statistics',
        ),
        'total':
        fields.function(
            _get_total,
            string='Total',
            type='integer',
        ),
        'scheduled':
        fields.function(
            _get_statistics,
            string='Scheduled',
            type='integer',
            multi='_get_statistics',
        ),
        'failed':
        fields.function(
            _get_statistics,
            string='Failed',
            type='integer',
            multi='_get_statistics',
        ),
        'sent':
        fields.function(
            _get_statistics,
            string='Sent',
            type='integer',
            multi='_get_statistics',
        ),
        'delivered':
        fields.function(
            _get_statistics,
            string='Delivered',
            type='integer',
            multi='_get_statistics',
        ),
        'opened':
        fields.function(
            _get_statistics,
            string='Opened',
            type='integer',
            multi='_get_statistics',
        ),
        'replied':
        fields.function(
            _get_statistics,
            string='Replied',
            type='integer',
            multi='_get_statistics',
        ),
        'bounced':
        fields.function(
            _get_statistics,
            string='Bounced',
            type='integer',
            multi='_get_statistics',
        ),
        'failed':
        fields.function(
            _get_statistics,
            string='Failed',
            type='integer',
            multi='_get_statistics',
        ),
        'received_ratio':
        fields.function(
            _get_statistics,
            string='Received Ratio',
            type='integer',
            multi='_get_statistics',
        ),
        'opened_ratio':
        fields.function(
            _get_statistics,
            string='Opened Ratio',
            type='integer',
            multi='_get_statistics',
        ),
        'replied_ratio':
        fields.function(
            _get_statistics,
            string='Replied Ratio',
            type='integer',
            multi='_get_statistics',
        ),
        'bounced_ratio':
        fields.function(
            _get_statistics,
            String='Bouncded Ratio',
            type='integer',
            multi='_get_statistics',
        ),
        'next_departure':
        fields.function(_get_next_departure,
                        string='Next Departure',
                        type='datetime'),
    }

    def mass_mailing_statistics_action(self, cr, uid, ids, context=None):
        res = self.pool['ir.actions.act_window'].for_xml_id(
            cr,
            uid,
            'mass_mailing',
            'action_view_mass_mailing_statistics',
            context=context)
        link_click_ids = self.pool['link.tracker.click'].search(
            cr, uid, [('mass_mailing_id', 'in', ids)], context=context)
        res['domain'] = [('id', 'in', link_click_ids)]
        return res

    def default_get(self, cr, uid, fields, context=None):
        res = super(MassMailing, self).default_get(cr,
                                                   uid,
                                                   fields,
                                                   context=context)
        if 'reply_to_mode' in fields and not 'reply_to_mode' in res and res.get(
                'mailing_model'):
            if res['mailing_model'] in [
                    'res.partner', 'mail.mass_mailing.contact'
            ]:
                res['reply_to_mode'] = 'email'
            else:
                res['reply_to_mode'] = 'thread'
        return res

    _defaults = {
        'active':
        True,
        'state':
        'draft',
        'email_from':
        lambda self, cr, uid, ctx=None: self.pool[
            'mail.message']._get_default_from(cr, uid, context=ctx),
        'reply_to':
        lambda self, cr, uid, ctx=None: self.pool['mail.message'].
        _get_default_from(cr, uid, context=ctx),
        'mailing_model':
        'mail.mass_mailing.contact',
        'contact_ab_pc':
        100,
        'mailing_domain': [],
    }

    def onchange_mass_mailing_campaign_id(self,
                                          cr,
                                          uid,
                                          id,
                                          mass_mailing_campaign_ids,
                                          context=None):
        if mass_mailing_campaign_ids:
            mass_mailing_campaign = self.pool[
                'mail.mass_mailing.campaign'].browse(cr,
                                                     uid,
                                                     mass_mailing_campaign_ids,
                                                     context=context)

            dic = {
                'campaign_id': mass_mailing_campaign[0].campaign_id.id,
                'source_id': mass_mailing_campaign[0].source_id.id,
                'medium_id': mass_mailing_campaign[0].medium_id.id
            }
            return {'value': dic}

    #------------------------------------------------------
    # Technical stuff
    #------------------------------------------------------

    def copy_data(self, cr, uid, id, default=None, context=None):
        mailing = self.browse(cr, uid, id, context=context)
        default = dict(default or {}, name=_('%s (copy)') % mailing.name)
        return super(MassMailing, self).copy_data(cr,
                                                  uid,
                                                  id,
                                                  default,
                                                  context=context)

    def read_group(self,
                   cr,
                   uid,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   context=None,
                   orderby=False,
                   lazy=True):
        """ Override read_group to always display all states. """
        if groupby and groupby[0] == "state":
            # Default result structure
            # states = self._get_state_list(cr, uid, context=context)
            states = [('draft', _('Draft')), ('in_queue', _('In Queue')),
                      ('sending', _('Sending')), ('done', _('Sent'))]
            read_group_all_states = [{
                '__context': {
                    'group_by': groupby[1:]
                },
                '__domain':
                domain + [('state', '=', state_value)],
                'state':
                state_value,
                'state_count':
                0,
            } for state_value, state_name in states]
            # Get standard results
            read_group_res = super(MassMailing,
                                   self).read_group(cr,
                                                    uid,
                                                    domain,
                                                    fields,
                                                    groupby,
                                                    offset=offset,
                                                    limit=limit,
                                                    context=context,
                                                    orderby=orderby)
            # Update standard results with default results
            result = []
            for state_value, state_name in states:
                res = filter(lambda x: x['state'] == state_value,
                             read_group_res)
                if not res:
                    res = filter(lambda x: x['state'] == state_value,
                                 read_group_all_states)
                res[0]['state'] = [state_value, state_name]
                result.append(res[0])
            return result
        else:
            return super(MassMailing, self).read_group(cr,
                                                       uid,
                                                       domain,
                                                       fields,
                                                       groupby,
                                                       offset=offset,
                                                       limit=limit,
                                                       context=context,
                                                       orderby=orderby)

    def update_opt_out(self,
                       cr,
                       uid,
                       mailing_id,
                       email,
                       res_ids,
                       value,
                       context=None):
        mailing = self.browse(cr, uid, mailing_id, context=context)
        model = self.pool[mailing.mailing_model]
        if 'opt_out' in model._fields:
            email_fname = 'email_from'
            if 'email' in model._fields:
                email_fname = 'email'
            ctx = dict(context or {}, active_test=False)
            record_ids = model.search(cr,
                                      uid, [('id', 'in', res_ids),
                                            (email_fname, 'ilike', email)],
                                      context=ctx)
            model.write(cr,
                        uid,
                        record_ids, {'opt_out': value},
                        context=context)

    #------------------------------------------------------
    # Views & Actions
    #------------------------------------------------------

    def on_change_model_and_list(self,
                                 cr,
                                 uid,
                                 ids,
                                 mailing_model,
                                 list_ids,
                                 context=None):
        value = {}
        if mailing_model == 'mail.mass_mailing.contact':
            mailing_list_ids = set()
            for item in list_ids:
                if isinstance(item, (int, long)):
                    mailing_list_ids.add(item)
                elif len(item) == 2 and item[0] == 4:  # 4, id
                    mailing_list_ids.add(item[1])
                elif len(item) == 3:  # 6, 0, ids
                    mailing_list_ids |= set(item[2])
            if mailing_list_ids:
                value[
                    'mailing_domain'] = "[('list_id', 'in', %s), ('opt_out', '=', False)]" % list(
                        mailing_list_ids)
            else:
                value['mailing_domain'] = "[('list_id', '=', False)]"
        elif 'opt_out' in self.pool[mailing_model]._fields:
            value['mailing_domain'] = "[('opt_out', '=', False)]"
        else:
            value['mailing_domain'] = []
        value['body_html'] = "on_change_model_and_list"
        return {'value': value}

    def action_duplicate(self, cr, uid, ids, context=None):
        copy_id = None
        for mid in ids:
            copy_id = self.copy(cr, uid, mid, context=context)
        if copy_id:
            return {
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'mail.mass_mailing',
                'res_id': copy_id,
                'context': context,
                'flags': {
                    'initial_mode': 'edit'
                },
            }
        return False

    def action_test_mailing(self, cr, uid, ids, context=None):
        ctx = dict(context, default_mass_mailing_id=ids[0])
        return {
            'name': _('Test Mailing'),
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'mail.mass_mailing.test',
            'target': 'new',
            'context': ctx,
        }

    #------------------------------------------------------
    # Email Sending
    #------------------------------------------------------

    def get_recipients(self, cr, uid, mailing, context=None):
        if mailing.mailing_domain:
            domain = eval(mailing.mailing_domain)
            res_ids = self.pool[mailing.mailing_model].search(cr,
                                                              uid,
                                                              domain,
                                                              context=context)
        else:
            res_ids = []
            domain = [('id', 'in', res_ids)]

        # randomly choose a fragment
        if mailing.contact_ab_pc < 100:
            contact_nbr = self.pool[mailing.mailing_model].search(
                cr, uid, domain, count=True, context=context)
            topick = int(contact_nbr / 100.0 * mailing.contact_ab_pc)
            if mailing.mass_mailing_campaign_id and mailing.mass_mailing_campaign_id.unique_ab_testing:
                already_mailed = self.pool[
                    'mail.mass_mailing.campaign'].get_recipients(
                        cr,
                        uid, [mailing.mass_mailing_campaign_id.id],
                        context=context)[mailing.mass_mailing_campaign_id.id]
            else:
                already_mailed = set([])
            remaining = set(res_ids).difference(already_mailed)
            if topick > len(remaining):
                topick = len(remaining)
            res_ids = random.sample(remaining, topick)
        return res_ids

    def get_remaining_recipients(self, cr, uid, mailing, context=None):
        res_ids = self.get_recipients(cr, uid, mailing, context=context)
        already_mailed = self.pool['mail.mail.statistics'].search_read(
            cr,
            uid, [('model', '=', mailing.mailing_model),
                  ('res_id', 'in', res_ids),
                  ('mass_mailing_id', '=', mailing.id)], ['res_id'],
            context=context)
        already_mailed_res_ids = [
            record['res_id'] for record in already_mailed
        ]
        return list(set(res_ids) - set(already_mailed_res_ids))

    def send_mail(self, cr, uid, ids, context=None):
        author_id = self.pool['res.users'].browse(
            cr, uid, uid, context=context).partner_id.id
        for mailing in self.browse(cr, uid, ids, context=context):
            # instantiate an email composer + send emails
            res_ids = self.get_remaining_recipients(cr,
                                                    uid,
                                                    mailing,
                                                    context=context)
            if not res_ids:
                raise UserError(_('Please select recipients.'))

            if context:
                comp_ctx = dict(context, active_ids=res_ids)
            else:
                comp_ctx = {'active_ids': res_ids}

            # Convert links in absolute URLs before the application of the shortener
            self.write(cr,
                       uid, [mailing.id], {
                           'body_html':
                           self.pool['mail.template']._replace_local_links(
                               cr, uid, mailing.body_html, context)
                       },
                       context=context)

            composer_values = {
                'author_id':
                author_id,
                'attachment_ids':
                [(4, attachment.id) for attachment in mailing.attachment_ids],
                'body':
                self.convert_links(cr, uid, [mailing.id],
                                   context=context)[mailing.id],
                'subject':
                mailing.name,
                'model':
                mailing.mailing_model,
                'email_from':
                mailing.email_from,
                'record_name':
                False,
                'composition_mode':
                'mass_mail',
                'mass_mailing_id':
                mailing.id,
                'mailing_list_ids':
                [(4, l.id) for l in mailing.contact_list_ids],
                'no_auto_thread':
                mailing.reply_to_mode != 'thread',
            }
            if mailing.reply_to_mode == 'email':
                composer_values['reply_to'] = mailing.reply_to

            composer_id = self.pool['mail.compose.message'].create(
                cr, uid, composer_values, context=comp_ctx)
            self.pool['mail.compose.message'].send_mail(cr,
                                                        uid, [composer_id],
                                                        auto_commit=True,
                                                        context=comp_ctx)
            self.write(cr,
                       uid, [mailing.id], {'state': 'done'},
                       context=context)
        return True

    def convert_links(self, cr, uid, ids, context=None):
        res = {}
        for mass_mailing in self.browse(cr, uid, ids, context=context):
            utm_mixin = mass_mailing.mass_mailing_campaign_id if mass_mailing.mass_mailing_campaign_id else mass_mailing
            html = mass_mailing.body_html if mass_mailing.body_html else ''

            vals = {'mass_mailing_id': mass_mailing.id}

            if mass_mailing.mass_mailing_campaign_id:
                vals[
                    'mass_mailing_campaign_id'] = mass_mailing.mass_mailing_campaign_id.id
            if utm_mixin.campaign_id:
                vals['campaign_id'] = utm_mixin.campaign_id.id
            if utm_mixin.source_id:
                vals['source_id'] = utm_mixin.source_id.id
            if utm_mixin.medium_id:
                vals['medium_id'] = utm_mixin.medium_id.id

            res[mass_mailing.id] = self.pool['link.tracker'].convert_links(
                cr,
                uid,
                html,
                vals,
                blacklist=['/unsubscribe_from_list'],
                context=context)

        return res

    def put_in_queue(self, cr, uid, ids, context=None):
        self.write(cr,
                   uid,
                   ids, {
                       'sent_date': fields.datetime.now(),
                       'state': 'in_queue'
                   },
                   context=context)

    def cancel_mass_mailing(self, cr, uid, ids, context=None):
        self.write(cr, uid, ids, {'state': 'draft'}, context=context)

    def retry_failed_mail(self, cr, uid, mass_mailing_ids, context=None):
        mail_mail_ids = self.pool.get('mail.mail').search(
            cr,
            uid, [('mailing_id', 'in', mass_mailing_ids),
                  ('state', '=', 'exception')],
            context=context)
        self.pool.get('mail.mail').unlink(cr,
                                          uid,
                                          mail_mail_ids,
                                          context=context)

        mail_mail_statistics_ids = self.pool.get(
            'mail.mail.statistics').search(
                cr, uid, [('mail_mail_id_int', 'in', mail_mail_ids)])
        self.pool.get('mail.mail.statistics').unlink(cr,
                                                     uid,
                                                     mail_mail_statistics_ids,
                                                     context=context)

        self.write(cr, uid, mass_mailing_ids, {'state': 'in_queue'})

    def _process_mass_mailing_queue(self, cr, uid, context=None):
        now = datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
        mass_mailing_ids = self.search(cr,
                                       uid, [('state', 'in',
                                              ('in_queue', 'sending')), '|',
                                             ('schedule_date', '<', now),
                                             ('schedule_date', '=', False)],
                                       context=context)

        for mass_mailing_id in mass_mailing_ids:
            mass_mailing_record = self.browse(cr,
                                              uid,
                                              mass_mailing_id,
                                              context=context)

            if len(
                    self.get_remaining_recipients(
                        cr, uid, mass_mailing_record, context=context)) > 0:
                self.write(cr,
                           uid, [mass_mailing_id], {'state': 'sending'},
                           context=context)
                self.send_mail(cr, uid, [mass_mailing_id], context=context)
            else:
                self.write(cr,
                           uid, [mass_mailing_id], {'state': 'done'},
                           context=context)
예제 #26
0
파일: order.py 프로젝트: osiell/OCB
class sale_quote_option(osv.osv):
    _name = "sale.quote.option"
    _description = "Quotation Option"
    _columns = {
        'template_id':
        fields.many2one('sale.quote.template',
                        'Quotation Template Reference',
                        ondelete='cascade',
                        select=True,
                        required=True),
        'name':
        fields.text('Description', required=True, translate=True),
        'product_id':
        fields.many2one('product.product',
                        'Product',
                        domain=[('sale_ok', '=', True)],
                        required=True),
        'website_description':
        fields.html('Option Description', translate=True),
        'price_unit':
        fields.float('Unit Price',
                     required=True,
                     digits_compute=dp.get_precision('Product Price')),
        'discount':
        fields.float('Discount (%)',
                     digits_compute=dp.get_precision('Discount')),
        'uom_id':
        fields.many2one('product.uom', 'Unit of Measure ', required=True),
        'quantity':
        fields.float('Quantity',
                     required=True,
                     digits_compute=dp.get_precision('Product UoS')),
    }
    _defaults = {
        'quantity': 1,
    }

    @api.onchange('product_id')
    def _onchange_product_id(self):
        if not self.product_id:
            return
        product = self.product_id
        self.price_unit = product.list_price
        self.website_description = product.product_tmpl_id.quote_description
        self.name = product.name
        self.uom_id = product.uom_id
        domain = {
            'uom_id':
            [('category_id', '=', self.product_id.uom_id.category_id.id)]
        }
        return {'domain': domain}

    @api.onchange('uom_id')
    def _onchange_product_uom(self):
        if not self.product_id:
            return
        if not self.uom_id:
            self.price_unit = 0.0
            return
        if self.uom_id.id != self.product_id.uom_id.id:
            new_price = self.product_id.uom_id._compute_price(
                self.product_id.uom_id.id, self.price_unit, self.uom_id.id)
            self.price_unit = new_price

    def on_change_product_id(self,
                             cr,
                             uid,
                             ids,
                             product,
                             uom_id=None,
                             context=None):
        vals, domain = {}, []
        product_obj = self.pool.get('product.product').browse(cr,
                                                              uid,
                                                              product,
                                                              context=context)
        name = product_obj.name
        if product_obj.description_sale:
            name += '\n' + product_obj.description_sale
        vals.update({
            'price_unit': product_obj.list_price,
            'website_description':
            product_obj.product_tmpl_id.quote_description,
            'name': name,
            'uom_id': uom_id or product_obj.uom_id.id,
        })
        uom_obj = self.pool.get('product.uom')
        if vals['uom_id'] != product_obj.uom_id.id:
            selected_uom = uom_obj.browse(cr,
                                          uid,
                                          vals['uom_id'],
                                          context=context)
            new_price = uom_obj._compute_price(cr, uid, product_obj.uom_id.id,
                                               vals['price_unit'],
                                               vals['uom_id'])
            vals['price_unit'] = new_price
        if not uom_id:
            domain = {
                'uom_id':
                [('category_id', '=', product_obj.uom_id.category_id.id)]
            }
        return {'value': vals, 'domain': domain}

    def product_uom_change(self, cr, uid, ids, product, uom_id, context=None):
        if not uom_id:
            return {'value': {'price_unit': 0.0, 'uom_id': False}}
        return self.on_change_product_id(cr,
                                         uid,
                                         ids,
                                         product,
                                         uom_id=uom_id,
                                         context=context)
예제 #27
0
class confirm_operation_wizard(models.TransientModel):
    _name = 'confirm.operation.wizard'
    _description = 'Wizard di Conferma operazione'

    _columns = {
        'message':
        fields.html(string="Conferma operazione", readonly=True, store=False)
    }

    def _default_message(self, cr, uid, context=None):
        res = ""
        if context is None:
            context = {}
        if 'action_not_protocol_pec' in context and context[
                'action_not_protocol_pec']:
            res = '<p>Vuoi archiviare questo messaggio PEC?</p>'
        if 'action_not_protocol_sharedmail' in context and context[
                'action_not_protocol_sharedmail']:
            res = '<p>Vuoi archiviare questo messaggio e-mail?</p>'
        if 'action_not_protocol_document' in context and context[
                'action_not_protocol_document']:
            res = '<p>Vuoi archiviare questo messaggio documento?</p>'
        return res

    _defaults = {'message': _default_message}

    def go_to_operation(self, cr, uid, ids, context=None):
        if context is None:
            context = {}

        if 'action_not_protocol_sharedmail' in context and context[
                'action_not_protocol_sharedmail']:
            message_obj = self.pool.get('mail.message')
            message = message_obj.browse(cr, SUPERUSER_ID,
                                         context['active_id'])
            if message.sharedmail_state == 'new':
                message_obj.write(cr, SUPERUSER_ID, context['active_id'],
                                  {'sharedmail_state': 'not_protocol'})
            else:
                raise orm.except_orm(
                    _("Avviso"),
                    _("Messaggio gia' archiviato in precedenza: aggiorna la pagina"
                      ))

        if 'action_not_protocol_pec' in context and context[
                'action_not_protocol_pec']:
            message_obj = self.pool.get('mail.message')
            message = message_obj.browse(cr, SUPERUSER_ID,
                                         context['active_id'])
            if message.pec_state == 'new':
                message_obj.write(cr, SUPERUSER_ID, context['active_id'],
                                  {'pec_state': 'not_protocol'})
            else:
                raise orm.except_orm(
                    _("Avviso"),
                    _("Messaggio gia' archiviato in precedenza: aggiorna la pagina"
                      ))

        if 'action_not_protocol_document' in context and context[
                'action_not_protocol_document']:
            document_obj = self.pool.get('gedoc.document')
            document = document_obj.browse(cr, SUPERUSER_ID,
                                           context['active_id'])
            if document.doc_protocol_state == 'new':
                document_obj.write(cr, SUPERUSER_ID, context['active_id'],
                                   {'doc_protocol_state': 'not_protocol'})
            else:
                raise orm.except_orm(
                    _("Avviso"),
                    _("Documento gia' archiviato in precedenza: aggiorna la pagina"
                      ))

        return True
예제 #28
0
class SaleOrderLine(orm.Model):
    """ADD HTML note to sale order lines"""

    _inherit = "sale.order.line"

    _columns = {'formatted_note': fields.html('Formatted Note')}
예제 #29
0
class ProductTemplate(orm.Model):

    _inherit = 'product.template'

    _columns = {'description': fields.html('Invoice description')}
예제 #30
0
class PaymentAcquirer(osv.Model):
    """ Acquirer Model. Each specific acquirer can extend the model by adding
    its own fields, using the acquirer_name as a prefix for the new fields.
    Using the required_if_provider='<name>' attribute on fields it is possible
    to have required fields that depend on a specific acquirer.

    Each acquirer has a link to an ir.ui.view record that is a template of
    a button used to display the payment form. See examples in ``payment_ogone``
    and ``payment_paypal`` modules.

    Methods that should be added in an acquirer-specific implementation:

     - ``<name>_form_generate_values(self, cr, uid, id, reference, amount, currency,
       partner_id=False, partner_values=None, tx_custom_values=None, context=None)``:
       method that generates the values used to render the form button template.
     - ``<name>_get_form_action_url(self, cr, uid, id, context=None):``: method
       that returns the url of the button form. It is used for example in
       ecommerce application, if you want to post some data to the acquirer.
     - ``<name>_compute_fees(self, cr, uid, id, amount, currency_id, country_id,
       context=None)``: computed the fees of the acquirer, using generic fields
       defined on the acquirer model (see fields definition).

    Each acquirer should also define controllers to handle communication between
    OpenERP and the acquirer. It generally consists in return urls given to the
    button form and that the acquirer uses to send the customer back after the
    transaction, with transaction details given as a POST request.
    """
    _name = 'payment.acquirer'
    _description = 'Payment Acquirer'

    def _get_providers(self, cr, uid, context=None):
        return []

    # indirection to ease inheritance
    _provider_selection = lambda self, *args, **kwargs: self._get_providers(
        *args, **kwargs)

    _columns = {
        'name':
        fields.char('Name', required=True, translate=True),
        'provider':
        fields.selection(_provider_selection, string='Provider',
                         required=True),
        'company_id':
        fields.many2one('res.company', 'Company', required=True),
        'pre_msg':
        fields.html(
            'Message',
            translate=True,
            help='Message displayed to explain and help the payment process.'),
        'post_msg':
        fields.html(
            'Thanks Message',
            help='Message displayed after having done the payment process.'),
        'validation':
        fields.selection(
            [('manual', 'Manual'), ('automatic', 'Automatic')],
            string='Process Method',
            help=
            'Static payments are payments like transfer, that require manual steps.'
        ),
        'view_template_id':
        fields.many2one('ir.ui.view', 'Form Button Template', required=True),
        'environment':
        fields.selection([('test', 'Test'), ('prod', 'Production')],
                         string='Environment',
                         oldname='env'),
        'website_published':
        fields.boolean(
            'Visible in Portal / Website',
            copy=False,
            help=
            "Make this payment acquirer available (Customer invoices, etc.)"),
        # Fees
        'fees_active':
        fields.boolean('Compute fees'),
        'fees_dom_fixed':
        fields.float('Fixed domestic fees'),
        'fees_dom_var':
        fields.float('Variable domestic fees (in percents)'),
        'fees_int_fixed':
        fields.float('Fixed international fees'),
        'fees_int_var':
        fields.float('Variable international fees (in percents)'),
    }

    _defaults = {
        'company_id':
        lambda self, cr, uid, obj, ctx=None: self.pool['res.users'].browse(
            cr, uid, uid).company_id.id,
        'environment':
        'test',
        'validation':
        'automatic',
        'website_published':
        True,
    }

    def _check_required_if_provider(self, cr, uid, ids, context=None):
        """ If the field has 'required_if_provider="<provider>"' attribute, then it
        required if record.provider is <provider>. """
        for acquirer in self.browse(cr, uid, ids, context=context):
            if any(
                    getattr(f, 'required_if_provider', None) ==
                    acquirer.provider and not acquirer[k]
                    for k, f in self._fields.items()):
                return False
        return True

    _constraints = [
        (_check_required_if_provider, 'Required fields not filled',
         ['required for this provider']),
    ]

    def get_form_action_url(self, cr, uid, id, context=None):
        """ Returns the form action URL, for form-based acquirer implementations. """
        acquirer = self.browse(cr, uid, id, context=context)
        if hasattr(self, '%s_get_form_action_url' % acquirer.provider):
            return getattr(self, '%s_get_form_action_url' % acquirer.provider)(
                cr, uid, id, context=context)
        return False

    def form_preprocess_values(self,
                               cr,
                               uid,
                               id,
                               reference,
                               amount,
                               currency_id,
                               tx_id,
                               partner_id,
                               partner_values,
                               tx_values,
                               context=None):
        """  Pre process values before giving them to the acquirer-specific render
        methods. Those methods will receive:

             - partner_values: will contain name, lang, email, zip, address, city,
               country_id (int or False), country (browse or False), phone, reference
             - tx_values: will contain reference, amount, currency_id (int or False),
               currency (browse or False), partner (browse or False)
        """
        acquirer = self.browse(cr, uid, id, context=context)

        if tx_id:
            tx = self.pool.get('payment.transaction').browse(cr,
                                                             uid,
                                                             tx_id,
                                                             context=context)
            tx_data = {
                'reference': tx.reference,
                'amount': tx.amount,
                'currency_id': tx.currency_id.id,
                'currency': tx.currency_id,
                'partner': tx.partner_id,
            }
            partner_data = {
                'name': tx.partner_name,
                'lang': tx.partner_lang,
                'email': tx.partner_email,
                'zip': tx.partner_zip,
                'address': tx.partner_address,
                'city': tx.partner_city,
                'country_id': tx.partner_country_id.id,
                'country': tx.partner_country_id,
                'phone': tx.partner_phone,
                'reference': tx.partner_reference,
                'state': None,
            }
        else:
            if partner_id:
                partner = self.pool['res.partner'].browse(cr,
                                                          uid,
                                                          partner_id,
                                                          context=context)
                partner_data = {
                    'name':
                    partner.name,
                    'lang':
                    partner.lang,
                    'email':
                    partner.email,
                    'zip':
                    partner.zip,
                    'city':
                    partner.city,
                    'address':
                    _partner_format_address(partner.street, partner.street2),
                    'country_id':
                    partner.country_id.id,
                    'country':
                    partner.country_id,
                    'phone':
                    partner.phone,
                    'state':
                    partner.state_id,
                }
            else:
                partner, partner_data = False, {}
            partner_data.update(partner_values)

            if currency_id:
                currency = self.pool['res.currency'].browse(cr,
                                                            uid,
                                                            currency_id,
                                                            context=context)
            else:
                currency = self.pool['res.users'].browse(
                    cr, uid, uid, context=context).company_id.currency_id
            tx_data = {
                'reference': reference,
                'amount': amount,
                'currency_id': currency.id,
                'currency': currency,
                'partner': partner,
            }

        # update tx values
        tx_data.update(tx_values)

        # update partner values
        if not partner_data.get('address'):
            partner_data['address'] = _partner_format_address(
                partner_data.get('street', ''),
                partner_data.get('street2', ''))
        if not partner_data.get('country') and partner_data.get('country_id'):
            partner_data['country'] = self.pool['res.country'].browse(
                cr, uid, partner_data.get('country_id'), context=context)
        partner_data.update({
            'first_name':
            _partner_split_name(partner_data['name'])[0],
            'last_name':
            _partner_split_name(partner_data['name'])[1],
        })

        # compute fees
        fees_method_name = '%s_compute_fees' % acquirer.provider
        if hasattr(self, fees_method_name):
            fees = getattr(self, fees_method_name)(cr,
                                                   uid,
                                                   id,
                                                   tx_data['amount'],
                                                   tx_data['currency_id'],
                                                   partner_data['country_id'],
                                                   context=None)
            tx_data['fees'] = float_round(fees, 2)

        return (partner_data, tx_data)

    def render(self,
               cr,
               uid,
               id,
               reference,
               amount,
               currency_id,
               tx_id=None,
               partner_id=False,
               partner_values=None,
               tx_values=None,
               context=None):
        """ Renders the form template of the given acquirer as a qWeb template.
        All templates will receive:

         - acquirer: the payment.acquirer browse record
         - user: the current user browse record
         - currency_id: id of the transaction currency
         - amount: amount of the transaction
         - reference: reference of the transaction
         - partner: the current partner browse record, if any (not necessarily set)
         - partner_values: a dictionary of partner-related values
         - tx_values: a dictionary of transaction related values that depends on
                      the acquirer. Some specific keys should be managed in each
                      provider, depending on the features it offers:

          - 'feedback_url': feedback URL, controler that manage answer of the acquirer
                            (without base url) -> FIXME
          - 'return_url': URL for coming back after payment validation (wihout
                          base url) -> FIXME
          - 'cancel_url': URL if the client cancels the payment -> FIXME
          - 'error_url': URL if there is an issue with the payment -> FIXME

         - context: OpenERP context dictionary

        :param string reference: the transaction reference
        :param float amount: the amount the buyer has to pay
        :param res.currency browse record currency: currency
        :param int tx_id: id of a transaction; if set, bypasses all other given
                          values and only render the already-stored transaction
        :param res.partner browse record partner_id: the buyer
        :param dict partner_values: a dictionary of values for the buyer (see above)
        :param dict tx_custom_values: a dictionary of values for the transction
                                      that is given to the acquirer-specific method
                                      generating the form values
        :param dict context: OpenERP context
        """
        if context is None:
            context = {}
        if tx_values is None:
            tx_values = {}
        if partner_values is None:
            partner_values = {}
        acquirer = self.browse(cr, uid, id, context=context)

        # pre-process values
        amount = float_round(amount, 2)
        partner_values, tx_values = self.form_preprocess_values(
            cr,
            uid,
            id,
            reference,
            amount,
            currency_id,
            tx_id,
            partner_id,
            partner_values,
            tx_values,
            context=context)

        # call <name>_form_generate_values to update the tx dict with acqurier specific values
        cust_method_name = '%s_form_generate_values' % (acquirer.provider)
        if hasattr(self, cust_method_name):
            method = getattr(self, cust_method_name)
            partner_values, tx_values = method(cr,
                                               uid,
                                               id,
                                               partner_values,
                                               tx_values,
                                               context=context)

        qweb_context = {
            'tx_url':
            context.get('tx_url',
                        self.get_form_action_url(cr, uid, id,
                                                 context=context)),
            'submit_class':
            context.get('submit_class', 'btn btn-link'),
            'submit_txt':
            context.get('submit_txt'),
            'acquirer':
            acquirer,
            'user':
            self.pool.get("res.users").browse(cr, uid, uid, context=context),
            'reference':
            tx_values['reference'],
            'amount':
            tx_values['amount'],
            'currency':
            tx_values['currency'],
            'partner':
            tx_values.get('partner'),
            'partner_values':
            partner_values,
            'tx_values':
            tx_values,
            'context':
            context,
        }

        # because render accepts view ids but not qweb -> need to use the xml_id
        return self.pool['ir.ui.view'].render(cr,
                                              uid,
                                              acquirer.view_template_id.xml_id,
                                              qweb_context,
                                              engine='ir.qweb',
                                              context=context)

    def _wrap_payment_block(self,
                            cr,
                            uid,
                            html_block,
                            amount,
                            currency_id,
                            context=None):
        payment_header = _('Pay safely online')
        amount_str = float_repr(
            amount,
            self.pool.get('decimal.precision').precision_get(
                cr, uid, 'Account'))
        currency = self.pool['res.currency'].browse(cr,
                                                    uid,
                                                    currency_id,
                                                    context=context)
        currency_str = currency.symbol or currency.name
        amount = u"%s %s" % (
            (currency_str, amount_str) if currency.position == 'before' else
            (amount_str, currency_str))
        result = u"""<div class="payment_acquirers">
                         <div class="payment_header">
                             <div class="payment_amount">%s</div>
                             %s
                         </div>
                         %%s
                     </div>""" % (amount, payment_header)
        return result % html_block.decode("utf-8")

    def render_payment_block(self,
                             cr,
                             uid,
                             reference,
                             amount,
                             currency_id,
                             tx_id=None,
                             partner_id=False,
                             partner_values=None,
                             tx_values=None,
                             company_id=None,
                             context=None):
        html_forms = []
        domain = [('website_published', '=', True),
                  ('validation', '=', 'automatic')]
        if company_id:
            domain.append(('company_id', '=', company_id))
        acquirer_ids = self.search(cr, uid, domain, context=context)
        for acquirer_id in acquirer_ids:
            button = self.render(cr, uid, acquirer_id, reference, amount,
                                 currency_id, tx_id, partner_id,
                                 partner_values, tx_values, context)
            html_forms.append(button)
        if not html_forms:
            return ''
        html_block = '\n'.join(filter(None, html_forms))
        return self._wrap_payment_block(cr,
                                        uid,
                                        html_block,
                                        amount,
                                        currency_id,
                                        context=context)
예제 #31
0
class DeliveryCompany(models.Model):
    _name = 'mft.delivery_company'

    _columns = {
        'name':
        fields.char(string='Delivery Company',
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'contact_name':
        fields.char(string='Contact Name',
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'contact_phone':
        fields.char(string='Contact Phone',
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'description':
        fields.html(string='Description',
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'state':
        fields.selection([
            ('draft', 'Draft'),
            ('toconfirm', 'Request Confirm'),
            ('confirmed', 'Confirmed'),
            ('cancel', 'Cancel'),
        ],
                         'State',
                         readonly=True,
                         copy=False),
        #'wo_ids': fields.one2many('mft.work_order','oem_id',string='成品工单')
    }

    _defaults = {'state': 'draft'}

    def action_toconfirm(self, cr, uid, ids, context=None):
        if context is None:
            context = {}
        self.write(cr, uid, ids, {'state': 'toconfirm'})
        return True

    def action_confirmed(self, cr, uid, ids, context=None):
        if context is None:
            context = {}
        self.write(cr, uid, ids, {'state': 'confirmed'})
        return True

    def action_recover(self, cr, uid, ids, context=None):
        if context is None:
            context = {}
        self.write(cr, uid, ids, {'state': 'draft'})
        return True

    def unlink(self, cr, uid, ids, context=None):
        product_names = self.read(cr, uid, ids, ['state'], context=context)
        unlink_ids = []
        for p in product_names:
            if p['state'] in ['draft']:
                unlink_ids.append(p['id'])
            else:
                raise osv.except_osv(_('Invalid Action!'), '只有草稿才能删除!')

        return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)