Example #1
0
class resource_calendar_leaves(osv.osv):
    _name = "resource.calendar.leaves"
    _description = "Leave Detail"
    _columns = {
        'name' : fields.char("Name"),
        'company_id' : fields.related('calendar_id','company_id',type='many2one',relation='res.company',string="Company", store=True, readonly=True),
        'calendar_id' : fields.many2one("resource.calendar", "Working Time"),
        'date_from' : fields.datetime('Start Date', required=True),
        'date_to' : fields.datetime('End Date', required=True),
        'resource_id' : fields.many2one("resource.resource", "Resource", help="If empty, this is a generic holiday for the company. If a resource is set, the holiday/leave is only for this resource"),
    }

    def check_dates(self, cr, uid, ids, context=None):
        for leave in self.browse(cr, uid, ids, context=context):
            if leave.date_from and leave.date_to and leave.date_from > leave.date_to:
                return False
        return True

    _constraints = [
        (check_dates, 'Error! leave start-date must be lower then leave end-date.', ['date_from', 'date_to'])
    ]

    def onchange_resource(self, cr, uid, ids, resource, context=None):
        result = {}
        if resource:
            resource_pool = self.pool.get('resource.resource')
            result['calendar_id'] = resource_pool.browse(cr, uid, resource, context=context).calendar_id.id
            return {'value': result}
        return {'value': {'calendar_id': []}}
Example #2
0
class Bravo(osv.Model):
    _name = 'test_new_api.bravo'
    _columns = {
        'alpha_id':
        fields.many2one('test_new_api.alpha'),
        # a related field with a non-trivial path
        'alpha_name':
        fields.related('alpha_id', 'name', type='char'),
        # a related field with a single field
        'related_alpha_id':
        fields.related('alpha_id', type='many2one', obj='test_new_api.alpha'),
        # a related field with a single field that is also a related field!
        'related_related_alpha_id':
        fields.related('related_alpha_id',
                       type='many2one',
                       obj='test_new_api.alpha'),
    }
Example #3
0
class report_document_user(osv.osv):
    _name = "report.document.user"
    _description = "Files details by Users"
    _auto = False
    _columns = {
        'name':
        fields.char('Year', size=64, readonly=True),
        'month':
        fields.selection([('01', 'January'), ('02', 'February'),
                          ('03', 'March'), ('04', 'April'), ('05', 'May'),
                          ('06', 'June'), ('07', 'July'), ('08', 'August'),
                          ('09', 'September'), ('10', 'October'),
                          ('11', 'November'), ('12', 'December')],
                         'Month',
                         readonly=True),
        'user_id':
        fields.many2one('res.users', 'Owner', readonly=True),
        'user':
        fields.related('user_id', 'name', type='char', size=64, readonly=True),
        'directory':
        fields.char('Directory', size=64, readonly=True),
        'datas_fname':
        fields.char('File Name', size=64, readonly=True),
        'create_date':
        fields.datetime('Date Created', readonly=True),
        'change_date':
        fields.datetime('Modified Date', readonly=True),
        'file_size':
        fields.integer('File Size', readonly=True),
        'nbr':
        fields.integer('# of Files', readonly=True),
        'type':
        fields.char('Directory Type', size=64, readonly=True),
    }

    def init(self, cr):
        tools.drop_view_if_exists(cr, 'report_document_user')
        cr.execute("""
            CREATE OR REPLACE VIEW report_document_user as (
                 SELECT
                     min(f.id) as id,
                     to_char(f.create_date, 'YYYY') as name,
                     to_char(f.create_date, 'MM') as month,
                     f.user_id as user_id,
                     count(*) as nbr,
                     d.name as directory,
                     f.datas_fname as datas_fname,
                     f.create_date as create_date,
                     f.file_size as file_size,
                     min(d.type) as type,
                     f.write_date as change_date
                 FROM ir_attachment f
                     left join document_directory d on (f.parent_id=d.id and d.name<>'')
                 group by to_char(f.create_date, 'YYYY'), to_char(f.create_date, 'MM'),d.name,f.parent_id,d.type,f.create_date,f.user_id,f.file_size,d.type,f.write_date,f.datas_fname
             )
        """)
Example #4
0
class delivery_carrier(orm.Model):
    _name = 'delivery.carrier'
    _inherit = ['delivery.carrier', 'website.published.mixin']

    _columns = {
        'website_description': fields.related('product_id', 'description_sale', type="text", string='Description for Online Quotations'),
    }
    _defaults = {
        'website_published': False
    }
Example #5
0
class stock_picking(osv.osv):
    _inherit = 'stock.picking'
    _columns = {
        'purchase_id':
        fields.related('move_lines',
                       'purchase_line_id',
                       'order_id',
                       string="Purchase Orders",
                       readonly=True,
                       relation="many2one"),
    }
Example #6
0
class product_bom(osv.osv):
    _inherit = 'mrp.bom'

    _columns = {
        'standard_price':
        fields.related('product_tmpl_id',
                       'standard_price',
                       type="float",
                       relation="product.product",
                       string="Standard Price",
                       store=False)
    }
Example #7
0
class project_task(osv.osv):
    _name = "project.task"
    _inherit = "project.task"
    _columns = {
        'procurement_id':
        fields.many2one('procurement.order',
                        'Procurement',
                        ondelete='set null'),
        'sale_line_id':
        fields.related('procurement_id',
                       'sale_line_id',
                       type='many2one',
                       relation='sale.order.line',
                       store=True,
                       string='Sales Order Line'),
    }

    def _validate_subflows(self, cr, uid, ids, context=None):
        proc_obj = self.pool.get("procurement.order")
        for task in self.browse(cr, uid, ids, context=context):
            if task.procurement_id:
                proc_obj.check(cr,
                               SUPERUSER_ID, [task.procurement_id.id],
                               context=context)

    def write(self, cr, uid, ids, values, context=None):
        """ When closing tasks, validate subflows. """
        res = super(project_task, self).write(cr,
                                              uid,
                                              ids,
                                              values,
                                              context=context)
        if values.get('stage_id'):
            stage = self.pool.get('project.task.type').browse(
                cr, uid, values.get('stage_id'), context=context)
            if stage.closed:
                self._validate_subflows(cr, uid, ids, context=context)
        return res

    def unlink(self, cr, uid, ids, context=None):
        if context is None:
            context = {}
        for task in self.browse(cr, uid, ids, context=context):
            if task.sale_line_id:
                raise UserError(
                    _('You cannot delete a task related to a Sale Order. You can only archive this task.'
                      ))
        res = super(project_task, self).unlink(cr, uid, ids, context)
        return res
Example #8
0
class hr_grant_badge_wizard(osv.TransientModel):
    _name = 'gamification.badge.user.wizard'
    _inherit = ['gamification.badge.user.wizard']

    _columns = {
        'employee_id':
        fields.many2one("hr.employee", string='Employee', required=True),
        'user_id':
        fields.related("employee_id",
                       "user_id",
                       type="many2one",
                       relation="res.users",
                       store=True,
                       string='User')
    }

    def action_grant_badge(self, cr, uid, ids, context=None):
        """Wizard action for sending a badge to a chosen employee"""
        if context is None:
            context = {}

        badge_user_obj = self.pool.get('gamification.badge.user')

        for wiz in self.browse(cr, uid, ids, context=context):
            if not wiz.user_id:
                raise UserError(
                    _('You can send badges only to employees linked to a user.'
                      ))

            if uid == wiz.user_id.id:
                raise UserError(_('You can not send a badge to yourself'))

            values = {
                'user_id': wiz.user_id.id,
                'sender_id': uid,
                'badge_id': wiz.badge_id.id,
                'employee_id': wiz.employee_id.id,
                'comment': wiz.comment,
            }

            badge_user = badge_user_obj.create(cr,
                                               uid,
                                               values,
                                               context=context)
            result = badge_user_obj._send_badge(cr,
                                                uid, [badge_user],
                                                context=context)
        return result
Example #9
0
class SaleOrderLine(osv.Model):
    _inherit = 'sale.order.line'
    _columns = {
        'sale_layout_cat_id':
        fields.many2one('sale_layout.category', string='Section'),
        'categ_sequence':
        fields.related('sale_layout_cat_id',
                       'sequence',
                       type='integer',
                       string='Layout Sequence',
                       store=True)
        #  Store is intentionally set in order to keep the "historic" order.
    }

    _order = 'order_id, categ_sequence, sale_layout_cat_id, sequence, id'

    def _prepare_order_line_invoice_line(self,
                                         cr,
                                         uid,
                                         line,
                                         account_id=False,
                                         context=None):
        """Save the layout when converting to an invoice line."""
        invoice_vals = super(SaleOrderLine,
                             self)._prepare_order_line_invoice_line(
                                 cr,
                                 uid,
                                 line,
                                 account_id=account_id,
                                 context=context)
        if line.sale_layout_cat_id:
            invoice_vals['sale_layout_cat_id'] = line.sale_layout_cat_id.id
        if line.categ_sequence:
            invoice_vals['categ_sequence'] = line.categ_sequence
        return invoice_vals

    @api.multi
    def _prepare_invoice_line(self, qty):
        """
        Prepare the dict of values to create the new invoice line for a sales order line.

        :param qty: float quantity to invoice
        """
        res = super(SaleOrderLine, self)._prepare_invoice_line(qty)
        if self.sale_layout_cat_id:
            res['sale_layout_cat_id'] = self.sale_layout_cat_id.id
        return res
Example #10
0
class gamification_badge_user(osv.Model):
    """User having received a badge"""

    _name = 'gamification.badge.user'
    _description = 'Gamification user badge'
    _order = "create_date desc"
    _rec_name = "badge_name"

    _columns = {
        'user_id': fields.many2one('res.users', string="User", required=True, ondelete="cascade"),
        'sender_id': fields.many2one('res.users', string="Sender", help="The user who has send the badge"),
        'badge_id': fields.many2one('gamification.badge', string='Badge', required=True, ondelete="cascade"),
        'challenge_id': fields.many2one('gamification.challenge', string='Challenge originating', help="If this badge was rewarded through a challenge"),
        'comment': fields.text('Comment'),
        'badge_name': fields.related('badge_id', 'name', type="char", string="Badge Name"),
        'create_date': fields.datetime('Created', readonly=True),
        'create_uid': fields.many2one('res.users', string='Creator', readonly=True),
    }


    def _send_badge(self, cr, uid, ids, context=None):
        """Send a notification to a user for receiving a badge

        Does not verify constrains on badge granting.
        The users are added to the owner_ids (create badge_user if needed)
        The stats counters are incremented
        :param ids: list(int) of badge users that will receive the badge
        """
        res = True
        temp_obj = self.pool.get('mail.template')
        user_obj = self.pool.get('res.users')
        template_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'gamification', 'email_template_badge_received')[1]
        for badge_user in self.browse(cr, uid, ids, context=context):
            template = temp_obj.get_email_template(cr, uid, template_id, badge_user.id, context=context)
            body_html = temp_obj.render_template(cr, uid, template.body_html, 'gamification.badge.user', badge_user.id, context=template._context)
            res = user_obj.message_post(
                cr, uid, badge_user.user_id.id,
                body=body_html,
                subtype='gamification.mt_badge_granted',
                partner_ids=[badge_user.user_id.partner_id.id],
                context=context)
        return res

    def create(self, cr, uid, vals, context=None):
        self.pool.get('gamification.badge').check_granting(cr, uid, badge_id=vals.get('badge_id'), context=context)
        return super(gamification_badge_user, self).create(cr, uid, vals, context=context)
Example #11
0
class stock_quant(osv.osv):
    _inherit = 'stock.quant'

    def _get_quants(self, cr, uid, ids, context=None):
        return self.pool.get('stock.quant').search(cr, uid, [('lot_id', 'in', ids)], context=context)

    _columns = {
        'removal_date': fields.related('lot_id', 'removal_date', type='datetime', string='Removal Date',
            store={
                'stock.quant': (lambda self, cr, uid, ids, ctx: ids, ['lot_id'], 20),
                'stock.production.lot': (_get_quants, ['removal_date'], 20),
            }),
    }

    def apply_removal_strategy(self, cr, uid, qty, move, ops=False, domain=None, removal_strategy='fifo', context=None):
        if removal_strategy == 'fefo':
            order = 'removal_date, in_date, id'
            return self._quants_get_order(cr, uid, qty, move, ops=ops, domain=domain, orderby=order, context=context)
        return super(stock_quant, self).apply_removal_strategy(cr, uid, qty, move, ops=ops, domain=domain,
                                                               removal_strategy=removal_strategy, context=context)
Example #12
0
class procurement_rule(osv.osv):
    _inherit = 'procurement.rule'

    def _get_action(self, cr, uid, context=None):
        result = super(procurement_rule, self)._get_action(cr, uid, context=context)
        return result + [('move', _('Move From Another Location'))]

    def _get_rules(self, cr, uid, ids, context=None):
        res = []
        for route in self.browse(cr, uid, ids):
            res += [x.id for x in route.pull_ids]
        return res

    _columns = {
        'location_id': fields.many2one('stock.location', 'Procurement Location'),
        'location_src_id': fields.many2one('stock.location', 'Source Location',
            help="Source location is action=move"),
        'route_id': fields.many2one('stock.location.route', 'Route',
            help="If route_id is False, the rule is global"),
        'procure_method': fields.selection([('make_to_stock', 'Take From Stock'), ('make_to_order', 'Create Procurement')], 'Move Supply Method', required=True, 
                                           help="""Determines the procurement method of the stock move that will be generated: whether it will need to 'take from the available stock' in its source location or needs to ignore its stock and create a procurement over there."""),
        'route_sequence': fields.related('route_id', 'sequence', string='Route Sequence',
            store={
                'stock.location.route': (_get_rules, ['sequence'], 10),
                'procurement.rule': (lambda self, cr, uid, ids, c={}: ids, ['route_id'], 10),
        }),
        'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type',
            help="Picking Type determines the way the picking should be shown in the view, reports, ..."),
        'delay': fields.integer('Number of Days'),
        'partner_address_id': fields.many2one('res.partner', 'Partner Address'),
        'propagate': fields.boolean('Propagate cancel and split', help='If checked, when the previous move of the move (which was generated by a next procurement) is cancelled or split, the move generated by this move will too'),
        'warehouse_id': fields.many2one('stock.warehouse', 'Served Warehouse', help='The warehouse this rule is for'),
        'propagate_warehouse_id': fields.many2one('stock.warehouse', 'Warehouse to Propagate', help="The warehouse to propagate on the created move/procurement, which can be different of the warehouse this rule is for (e.g for resupplying rules from another warehouse)"),
    }

    _defaults = {
        'procure_method': 'make_to_stock',
        'propagate': True,
        'delay': 0,
    }
Example #13
0
class marketing_campaign_workitem(osv.osv):
    _name = "marketing.campaign.workitem"
    _description = "Campaign Workitem"

    def _res_name_get(self, cr, uid, ids, field_name, arg, context=None):
        res = dict.fromkeys(ids, '/')
        for wi in self.browse(cr, uid, ids, context=context):
            if not wi.res_id:
                continue

            proxy = self.pool[wi.object_id.model]
            if not proxy.exists(cr, uid, [wi.res_id]):
                continue
            ng = proxy.name_get(cr, uid, [wi.res_id], context=context)
            if ng:
                res[wi.id] = ng[0][1]
        return res

    def _resource_search(self, cr, uid, obj, name, args, domain=None, context=None):
        """Returns id of workitem whose resource_name matches  with the given name"""
        if not len(args):
            return []

        condition_name = None
        for domain_item in args:
            # we only use the first domain criterion and ignore all the rest including operators
            if isinstance(domain_item, (list,tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_name':
                condition_name = [None, domain_item[1], domain_item[2]]
                break

        assert condition_name, "Invalid search domain for marketing_campaign_workitem.res_name. It should use 'res_name'"

        cr.execute("""select w.id, w.res_id, m.model  \
                                from marketing_campaign_workitem w \
                                    left join marketing_campaign_activity a on (a.id=w.activity_id)\
                                    left join marketing_campaign c on (c.id=a.campaign_id)\
                                    left join ir_model m on (m.id=c.object_id)
                                    """)
        res = cr.fetchall()
        workitem_map = {}
        matching_workitems = []
        for id, res_id, model in res:
            workitem_map.setdefault(model,{}).setdefault(res_id,set()).add(id)
        for model, id_map in workitem_map.iteritems():
            model_pool = self.pool[model]
            condition_name[0] = model_pool._rec_name
            condition = [('id', 'in', id_map.keys()), condition_name]
            for res_id in model_pool.search(cr, uid, condition, context=context):
                matching_workitems.extend(id_map[res_id])
        return [('id', 'in', list(set(matching_workitems)))]

    _columns = {
        'segment_id': fields.many2one('marketing.campaign.segment', 'Segment', readonly=True),
        'activity_id': fields.many2one('marketing.campaign.activity','Activity',
             required=True, readonly=True),
        'campaign_id': fields.related('activity_id', 'campaign_id',
             type='many2one', relation='marketing.campaign', string='Campaign', readonly=True, store=True),
        'object_id': fields.related('activity_id', 'campaign_id', 'object_id',
             type='many2one', relation='ir.model', string='Resource', select=1, readonly=True, store=True),
        'res_id': fields.integer('Resource ID', select=1, readonly=True),
        'res_name': fields.function(_res_name_get, string='Resource Name', fnct_search=_resource_search, type="char", size=64),
        'date': fields.datetime('Execution Date', help='If date is not set, this workitem has to be run manually', readonly=True),
        'partner_id': fields.many2one('res.partner', 'Partner', select=1, readonly=True),
        'state': fields.selection([ ('todo', 'To Do'),
                                    ('cancelled', 'Cancelled'),
                                    ('exception', 'Exception'),
                                    ('done', 'Done'),
                                   ], 'Status', readonly=True, copy=False),
        'error_msg' : fields.text('Error Message', readonly=True)
    }
    _defaults = {
        'state': lambda *a: 'todo',
        'date': False,
    }

    @api.cr_uid_ids_context
    def button_draft(self, cr, uid, workitem_ids, context=None):
        for wi in self.browse(cr, uid, workitem_ids, context=context):
            if wi.state in ('exception', 'cancelled'):
                self.write(cr, uid, [wi.id], {'state':'todo'}, context=context)
        return True

    @api.cr_uid_ids_context
    def button_cancel(self, cr, uid, workitem_ids, context=None):
        for wi in self.browse(cr, uid, workitem_ids, context=context):
            if wi.state in ('todo','exception'):
                self.write(cr, uid, [wi.id], {'state':'cancelled'}, context=context)
        return True

    def _process_one(self, cr, uid, workitem, context=None):
        if workitem.state != 'todo':
            return False

        activity = workitem.activity_id
        proxy = self.pool[workitem.object_id.model]
        object_id = proxy.browse(cr, uid, workitem.res_id, context=context)

        eval_context = {
            'activity': activity,
            'workitem': workitem,
            'object': object_id,
            'resource': object_id,
            'transitions': activity.to_ids,
            're': re,
        }
        try:
            condition = activity.condition
            campaign_mode = workitem.campaign_id.mode
            if condition:
                if not eval(condition, eval_context):
                    if activity.keep_if_condition_not_met:
                        workitem.write({'state': 'cancelled'})
                    else:
                        workitem.unlink()
                    return
            result = True
            if campaign_mode in ('manual', 'active'):
                Activities = self.pool.get('marketing.campaign.activity')
                result = Activities.process(cr, uid, activity.id, workitem.id,
                                            context=context)

            values = dict(state='done')
            if not workitem.date:
                values['date'] = datetime.now().strftime(DT_FMT)
            workitem.write(values)

            if result:
                # process _chain
                workitem.refresh()       # reload
                date = datetime.strptime(workitem.date, DT_FMT)

                for transition in activity.to_ids:
                    if transition.trigger == 'cosmetic':
                        continue
                    launch_date = False
                    if transition.trigger == 'auto':
                        launch_date = date
                    elif transition.trigger == 'time':
                        launch_date = date + transition._delta()

                    if launch_date:
                        launch_date = launch_date.strftime(DT_FMT)
                    values = {
                        'date': launch_date,
                        'segment_id': workitem.segment_id.id,
                        'activity_id': transition.activity_to_id.id,
                        'partner_id': workitem.partner_id.id,
                        'res_id': workitem.res_id,
                        'state': 'todo',
                    }
                    wi_id = self.create(cr, uid, values, context=context)

                    # Now, depending on the trigger and the campaign mode
                    # we know whether we must run the newly created workitem.
                    #
                    # rows = transition trigger \ colums = campaign mode
                    #
                    #           test    test_realtime     manual      normal (active)
                    # time       Y            N             N           N
                    # cosmetic   N            N             N           N
                    # auto       Y            Y             N           Y
                    #

                    run = (transition.trigger == 'auto' \
                            and campaign_mode != 'manual') \
                          or (transition.trigger == 'time' \
                              and campaign_mode == 'test')
                    if run:
                        new_wi = self.browse(cr, uid, wi_id, context)
                        self._process_one(cr, uid, new_wi, context)

        except Exception:
            tb = "".join(format_exception(*exc_info()))
            workitem.write({'state': 'exception', 'error_msg': tb})

    @api.cr_uid_ids_context
    def process(self, cr, uid, workitem_ids, context=None):
        for wi in self.browse(cr, uid, workitem_ids, context=context):
            self._process_one(cr, uid, wi, context=context)
        return True

    def process_all(self, cr, uid, camp_ids=None, context=None):
        camp_obj = self.pool.get('marketing.campaign')
        if camp_ids is None:
            camp_ids = camp_obj.search(cr, uid, [('state','=','running')], context=context)
        for camp in camp_obj.browse(cr, uid, camp_ids, context=context):
            if camp.mode == 'manual':
                # manual states are not processed automatically
                continue
            while True:
                domain = [('campaign_id', '=', camp.id), ('state', '=', 'todo'), ('date', '!=', False)]
                if camp.mode in ('test_realtime', 'active'):
                    domain += [('date','<=', time.strftime('%Y-%m-%d %H:%M:%S'))]

                workitem_ids = self.search(cr, uid, domain, context=context)
                if not workitem_ids:
                    break

                self.process(cr, uid, workitem_ids, context=context)
        return True

    def preview(self, cr, uid, ids, context=None):
        res = {}
        wi_obj = self.browse(cr, uid, ids[0], context=context)
        if wi_obj.activity_id.type == 'email':
            view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'email_template_preview_form')
            res = {
                'name': _('Email Preview'),
                'view_type': 'form',
                'view_mode': 'form,tree',
                'res_model': 'email_template.preview',
                'view_id': False,
                'context': context,
                'views': [(view_id and view_id[1] or 0, 'form')],
                'type': 'ir.actions.act_window',
                'target': 'new',
                'context': "{'template_id':%d,'default_res_id':%d}"%
                                (wi_obj.activity_id.email_template_id.id,
                                 wi_obj.res_id)
            }

        elif wi_obj.activity_id.type == 'report':
            datas = {
                'ids': [wi_obj.res_id],
                'model': wi_obj.object_id.model
            }
            res = {
                'type' : 'ir.actions.report.xml',
                'report_name': wi_obj.activity_id.report_id.report_name,
                'datas' : datas,
            }
        else:
            raise UserError(_('The current step for this item has no email or report to preview.'))
        return res
Example #14
0
class task(osv.osv):
    _inherit = "project.task"

    # Compute: effective_hours, total_hours, progress
    def _hours_get(self, cr, uid, ids, field_names, args, context=None):
        res = {}
        tasks_data = self.pool['account.analytic.line'].read_group(
            cr,
            uid, [('task_id', 'in', ids)], ['task_id', 'unit_amount'],
            ['task_id'],
            context=context)
        for data in tasks_data:
            task = self.browse(cr, uid, data['task_id'][0], context=context)
            res[data['task_id'][0]] = {
                'effective_hours':
                data.get('unit_amount', 0.0),
                'remaining_hours':
                task.planned_hours - data.get('unit_amount', 0.0)
            }
            res[data['task_id'][0]]['total_hours'] = res[
                data['task_id'][0]]['remaining_hours'] + data.get(
                    'unit_amount', 0.0)
            res[data['task_id'][0]]['delay_hours'] = res[
                data['task_id'][0]]['total_hours'] - task.planned_hours
            res[data['task_id'][0]]['progress'] = 0.0
            if (task.planned_hours > 0.0 and data.get('unit_amount', 0.0)):
                res[data['task_id'][0]]['progress'] = round(
                    min(
                        100.0 * data.get('unit_amount', 0.0) /
                        task.planned_hours, 99.99), 2)
            # TDE CHECK: if task.state in ('done','cancelled'):
            if task.stage_id and task.stage_id.fold:
                res[data['task_id'][0]]['progress'] = 100.0
        return res

    def _get_task(self, cr, uid, id, context=None):
        res = []
        for line in self.pool.get('account.analytic.line').search_read(
                cr,
                uid, [('task_id', '!=', False), ('id', 'in', id)],
                context=context):
            res.append(line['task_id'][0])
        return res

    def _get_total_hours(self):
        return super(task, self)._get_total_hours() + self.effective_hours

    _columns = {
        'remaining_hours':
        fields.function(
            _hours_get,
            string='Remaining Hours',
            multi='line_id',
            help=
            "Total remaining time, can be re-estimated periodically by the assignee of the task.",
            store={
                'project.task':
                (lambda self, cr, uid, ids, c={}: ids,
                 ['timesheet_ids', 'remaining_hours', 'planned_hours'], 10),
                'account.analytic.line':
                (_get_task, ['task_id', 'unit_amount'], 10),
            }),
        'effective_hours':
        fields.function(_hours_get,
                        string='Hours Spent',
                        multi='line_id',
                        help="Computed using the sum of the task work done.",
                        store={
                            'project.task':
                            (lambda self, cr, uid, ids, c={}: ids, [
                                'timesheet_ids', 'remaining_hours',
                                'planned_hours'
                            ], 10),
                            'account.analytic.line':
                            (_get_task, ['task_id', 'unit_amount'], 10),
                        }),
        'total_hours':
        fields.function(_hours_get,
                        string='Total',
                        multi='line_id',
                        help="Computed as: Time Spent + Remaining Time.",
                        store={
                            'project.task':
                            (lambda self, cr, uid, ids, c={}: ids, [
                                'timesheet_ids', 'remaining_hours',
                                'planned_hours'
                            ], 10),
                            'account.analytic.line':
                            (_get_task, ['task_id', 'unit_amount'], 10),
                        }),
        'progress':
        fields.function(
            _hours_get,
            string='Working Time Progress (%)',
            multi='line_id',
            group_operator="avg",
            help=
            "If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
            store={
                'project.task': (lambda self, cr, uid, ids, c={}: ids, [
                    'timesheet_ids', 'remaining_hours', 'planned_hours',
                    'state', 'stage_id'
                ], 10),
                'account.analytic.line':
                (_get_task, ['task_id', 'unit_amount'], 10),
            }),
        'delay_hours':
        fields.function(
            _hours_get,
            string='Delay Hours',
            multi='line_id',
            help=
            "Computed as difference between planned hours by the project manager and the total hours of the task.",
            store={
                'project.task':
                (lambda self, cr, uid, ids, c={}: ids,
                 ['timesheet_ids', 'remaining_hours', 'planned_hours'], 10),
                'account.analytic.line':
                (_get_task, ['task_id', 'unit_amount'], 10),
            }),
        'timesheet_ids':
        fields.one2many('account.analytic.line', 'task_id', 'Timesheets'),
        'analytic_account_id':
        fields.related('project_id',
                       'analytic_account_id',
                       type='many2one',
                       relation='account.analytic.account',
                       string='Analytic Account',
                       store=True),
    }

    _defaults = {
        'progress': 0,
    }

    def _prepare_delegate_values(self,
                                 cr,
                                 uid,
                                 ids,
                                 delegate_data,
                                 context=None):
        vals = super(task,
                     self)._prepare_delegate_values(cr, uid, ids,
                                                    delegate_data, context)
        for task in self.browse(cr, uid, ids, context=context):
            vals[task.id]['planned_hours'] += task.effective_hours
        return vals

    def onchange_project(self, cr, uid, ids, project_id, context=None):
        result = super(task, self).onchange_project(cr,
                                                    uid,
                                                    ids,
                                                    project_id,
                                                    context=context)
        if not project_id:
            return result
        if 'value' not in result:
            result['value'] = {}
        project = self.pool['project.project'].browse(cr,
                                                      uid,
                                                      project_id,
                                                      context=context)
        return result
Example #15
0
class mrp_production_workcenter_line(osv.osv):
    def _get_date_end(self, cr, uid, ids, field_name, arg, context=None):
        """ Finds ending date.
        @return: Dictionary of values.
        """
        ops = self.browse(cr, uid, ids, context=context)
        date_and_hours_by_cal = [(op.date_planned, op.hour,
                                  op.workcenter_id.calendar_id.id)
                                 for op in ops if op.date_planned]

        intervals = self.pool.get('resource.calendar').interval_get_multi(
            cr, uid, date_and_hours_by_cal)

        res = {}
        for op in ops:
            res[op.id] = False
            if op.date_planned:
                i = intervals.get((op.date_planned, op.hour,
                                   op.workcenter_id.calendar_id.id))
                if i:
                    res[op.id] = i[-1][1].strftime('%Y-%m-%d %H:%M:%S')
                else:
                    res[op.id] = op.date_planned
        return res

    def onchange_production_id(self,
                               cr,
                               uid,
                               ids,
                               production_id,
                               context=None):
        if not production_id:
            return {}
        production = self.pool.get('mrp.production').browse(cr,
                                                            uid,
                                                            production_id,
                                                            context=None)
        result = {
            'product': production.product_id.id,
            'qty': production.product_qty,
            'uom': production.product_uom.id,
        }
        return {'value': result}

    _inherit = 'mrp.production.workcenter.line'
    _order = "sequence, date_planned"

    _columns = {
       'state': fields.selection([('draft','Draft'),('cancel','Cancelled'),('pause','Pending'),('startworking', 'In Progress'),('done','Finished')],'Status', readonly=True, copy=False,
                                 help="* When a work order is created it is set in 'Draft' status.\n" \
                                       "* When user sets work order in start mode that time it will be set in 'In Progress' status.\n" \
                                       "* When work order is in running mode, during that time if user wants to stop or to make changes in order then can set in 'Pending' status.\n" \
                                       "* When the user cancels the work order it will be set in 'Canceled' status.\n" \
                                       "* When order is completely processed that time it is set in 'Finished' status."),
       'date_planned': fields.datetime('Scheduled Date', select=True),
       'date_planned_end': fields.function(_get_date_end, string='End Date', type='datetime'),
       'date_start': fields.datetime('Start Date'),
       'date_finished': fields.datetime('End Date'),
       'delay': fields.float('Working Hours',help="The elapsed time between operation start and stop in this Work Center",readonly=True),
       'production_state':fields.related('production_id','state',
            type='selection',
            selection=[('draft','Draft'),('confirmed','Waiting Goods'),('ready','Ready to Produce'),('in_production','In Production'),('cancel','Canceled'),('done','Done')],
            string='Production Status', readonly=True),
       'product':fields.related('production_id','product_id',type='many2one',relation='product.product',string='Product',
            readonly=True),
       'qty':fields.related('production_id','product_qty',type='float',string='Qty',readonly=True, store=True),
       'uom':fields.related('production_id','product_uom',type='many2one',relation='product.uom',string='Unit of Measure',readonly=True),
    }

    _defaults = {'state': 'draft', 'delay': 0.0, 'production_state': 'draft'}

    def modify_production_order_state(self, cr, uid, ids, action):
        """ Modifies production order state if work order state is changed.
        @param action: Action to perform.
        @return: Nothing
        """
        prod_obj_pool = self.pool.get('mrp.production')
        oper_obj = self.browse(cr, uid, ids)[0]
        prod_obj = oper_obj.production_id
        if action == 'start':
            if prod_obj.state == 'confirmed':
                prod_obj_pool.force_production(cr, uid, [prod_obj.id])
                prod_obj_pool.signal_workflow(cr, uid, [prod_obj.id],
                                              'button_produce')
            elif prod_obj.state == 'ready':
                prod_obj_pool.signal_workflow(cr, uid, [prod_obj.id],
                                              'button_produce')
            elif prod_obj.state == 'in_production':
                return
            else:
                raise UserError(
                    _('Manufacturing order cannot be started in state "%s"!') %
                    (prod_obj.state, ))
        else:
            open_count = self.search_count(
                cr, uid, [('production_id', '=', prod_obj.id),
                          ('state', '!=', 'done')])
            flag = not bool(open_count)
            if flag:
                button_produce_done = True
                for production in prod_obj_pool.browse(cr,
                                                       uid, [prod_obj.id],
                                                       context=None):
                    if production.move_lines or production.move_created_ids:
                        moves = production.move_lines + production.move_created_ids
                        # If tracking is activated, we want to make sure the user will enter the
                        # serial numbers.
                        if moves.filtered(
                                lambda r: r.product_id.tracking != 'none'):
                            button_produce_done = False
                        else:
                            prod_obj_pool.action_produce(
                                cr,
                                uid,
                                production.id,
                                production.product_qty,
                                'consume_produce',
                                context=None)
                if button_produce_done:
                    prod_obj_pool.signal_workflow(cr, uid,
                                                  [oper_obj.production_id.id],
                                                  'button_produce_done')
        return

    def write(self, cr, uid, ids, vals, context=None, update=True):
        result = super(mrp_production_workcenter_line,
                       self).write(cr, uid, ids, vals, context=context)
        prod_obj = self.pool.get('mrp.production')
        if vals.get('date_planned', False) and update:
            for prod in self.browse(cr, uid, ids, context=context):
                if prod.production_id.workcenter_lines:
                    dstart = min(
                        vals['date_planned'],
                        prod.production_id.workcenter_lines[0]['date_planned'])
                    prod_obj.write(cr,
                                   uid, [prod.production_id.id],
                                   {'date_start': dstart},
                                   context=context,
                                   mini=False)
        return result

    def action_draft(self, cr, uid, ids, context=None):
        """ Sets state to draft.
        @return: True
        """
        return self.write(cr, uid, ids, {'state': 'draft'}, context=context)

    def action_start_working(self, cr, uid, ids, context=None):
        """ Sets state to start working and writes starting date.
        @return: True
        """
        self.modify_production_order_state(cr, uid, ids, 'start')
        self.write(cr,
                   uid,
                   ids, {
                       'state': 'startworking',
                       'date_start': time.strftime('%Y-%m-%d %H:%M:%S')
                   },
                   context=context)
        return True

    def action_done(self, cr, uid, ids, context=None):
        """ Sets state to done, writes finish date and calculates delay.
        @return: True
        """
        delay = 0.0
        date_now = time.strftime('%Y-%m-%d %H:%M:%S')
        obj_line = self.browse(cr, uid, ids[0])

        date_start = datetime.strptime(obj_line.date_start,
                                       '%Y-%m-%d %H:%M:%S')
        date_finished = datetime.strptime(date_now, '%Y-%m-%d %H:%M:%S')
        delay += (date_finished - date_start).days * 24
        delay += (date_finished - date_start).seconds / float(60 * 60)

        self.write(cr,
                   uid,
                   ids, {
                       'state': 'done',
                       'date_finished': date_now,
                       'delay': delay
                   },
                   context=context)
        self.modify_production_order_state(cr, uid, ids, 'done')
        return True

    def action_cancel(self, cr, uid, ids, context=None):
        """ Sets state to cancel.
        @return: True
        """
        return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)

    def action_pause(self, cr, uid, ids, context=None):
        """ Sets state to pause.
        @return: True
        """
        return self.write(cr, uid, ids, {'state': 'pause'}, context=context)

    def action_resume(self, cr, uid, ids, context=None):
        """ Sets state to startworking.
        @return: True
        """
        return self.write(cr,
                          uid,
                          ids, {'state': 'startworking'},
                          context=context)
Example #16
0
class gamification_goal(osv.Model):
    """Goal instance for a user

    An individual goal for a user on a specified time period"""

    _name = 'gamification.goal'
    _description = 'Gamification goal instance'

    def _get_completion(self, cr, uid, ids, field_name, arg, context=None):
        """Return the percentage of completeness of the goal, between 0 and 100"""
        res = dict.fromkeys(ids, 0.0)
        for goal in self.browse(cr, uid, ids, context=context):
            if goal.definition_condition == 'higher':
                if goal.current >= goal.target_goal:
                    res[goal.id] = 100.0
                else:
                    res[goal.id] = round(
                        100.0 * goal.current / goal.target_goal, 2)
            elif goal.current < goal.target_goal:
                # a goal 'lower than' has only two values possible: 0 or 100%
                res[goal.id] = 100.0
            else:
                res[goal.id] = 0.0
        return res

    def on_change_definition_id(self,
                                cr,
                                uid,
                                ids,
                                definition_id=False,
                                context=None):
        goal_definition = self.pool.get('gamification.goal.definition')
        if not definition_id:
            return {'value': {'definition_id': False}}
        goal_definition = goal_definition.browse(cr,
                                                 uid,
                                                 definition_id,
                                                 context=context)
        return {
            'value': {
                'computation_mode': goal_definition.computation_mode,
                'definition_condition': goal_definition.condition
            }
        }

    _columns = {
        'definition_id':
        fields.many2one('gamification.goal.definition',
                        string='Goal Definition',
                        required=True,
                        ondelete="cascade"),
        'user_id':
        fields.many2one('res.users',
                        string='User',
                        required=True,
                        auto_join=True,
                        ondelete="cascade"),
        'line_id':
        fields.many2one('gamification.challenge.line',
                        string='Challenge Line',
                        ondelete="cascade"),
        'challenge_id':
        fields.related(
            'line_id',
            'challenge_id',
            string="Challenge",
            type='many2one',
            relation='gamification.challenge',
            store=True,
            readonly=True,
            help=
            "Challenge that generated the goal, assign challenge to users to generate goals with a value in this field."
        ),
        'start_date':
        fields.date('Start Date'),
        'end_date':
        fields.date('End Date'),  # no start and end = always active
        'target_goal':
        fields.float('To Reach', required=True,
                     track_visibility='always'),  # no goal = global index
        'current':
        fields.float('Current Value', required=True,
                     track_visibility='always'),
        'completeness':
        fields.function(_get_completion, type='float', string='Completeness'),
        'state':
        fields.selection([
            ('draft', 'Draft'),
            ('inprogress', 'In progress'),
            ('reached', 'Reached'),
            ('failed', 'Failed'),
            ('canceled', 'Canceled'),
        ],
                         string='State',
                         required=True,
                         track_visibility='always'),
        'to_update':
        fields.boolean('To update'),
        'closed':
        fields.boolean('Closed goal',
                       help="These goals will not be recomputed."),
        'computation_mode':
        fields.related('definition_id',
                       'computation_mode',
                       type='char',
                       string="Computation mode"),
        'remind_update_delay':
        fields.integer(
            'Remind delay',
            help=
            "The number of days after which the user assigned to a manual goal will be reminded. Never reminded if no value is specified."
        ),
        'last_update':
        fields.date(
            'Last Update',
            help=
            "In case of manual goal, reminders are sent if the goal as not been updated for a while (defined in challenge). Ignored in case of non-manual goal or goal not linked to a challenge."
        ),
        'definition_description':
        fields.related('definition_id',
                       'description',
                       type='char',
                       string='Definition Description',
                       readonly=True),
        'definition_condition':
        fields.related('definition_id',
                       'condition',
                       type='char',
                       string='Definition Condition',
                       readonly=True),
        'definition_suffix':
        fields.related('definition_id',
                       'full_suffix',
                       type="char",
                       string="Suffix",
                       readonly=True),
        'definition_display':
        fields.related('definition_id',
                       'display_mode',
                       type="char",
                       string="Display Mode",
                       readonly=True),
    }

    _defaults = {
        'current': 0,
        'state': 'draft',
        'start_date': fields.date.today,
    }
    _order = 'start_date desc, end_date desc, definition_id, id'

    def _check_remind_delay(self, cr, uid, goal, context=None):
        """Verify if a goal has not been updated for some time and send a
        reminder message of needed.

        :return: data to write on the goal object
        """
        temp_obj = self.pool['mail.template']
        if goal.remind_update_delay and goal.last_update:
            delta_max = timedelta(days=goal.remind_update_delay)
            last_update = datetime.strptime(goal.last_update, DF).date()
            if date.today() - last_update > delta_max:
                # generate a remind report
                temp_obj = self.pool.get('mail.template')
                template_id = self.pool['ir.model.data'].get_object_reference(
                    cr, uid, 'gamification', 'email_template_goal_reminder')[0]
                template = temp_obj.get_email_template(cr,
                                                       uid,
                                                       template_id,
                                                       goal.id,
                                                       context=context)
                body_html = temp_obj.render_template(cr,
                                                     uid,
                                                     template.body_html,
                                                     'gamification.goal',
                                                     goal.id,
                                                     context=template._context)
                self.pool['mail.thread'].message_post(
                    cr,
                    uid,
                    0,
                    body=body_html,
                    partner_ids=[goal.user_id.partner_id.id],
                    context=context,
                    subtype='mail.mt_comment')
                return {'to_update': True}
        return {}

    def _get_write_values(self, cr, uid, goal, new_value, context=None):
        """Generate values to write after recomputation of a goal score"""
        if new_value == goal.current:
            # avoid useless write if the new value is the same as the old one
            return {}

        result = {goal.id: {'current': new_value}}
        if (goal.definition_id.condition == 'higher' and new_value >= goal.target_goal) \
          or (goal.definition_id.condition == 'lower' and new_value <= goal.target_goal):
            # success, do no set closed as can still change
            result[goal.id]['state'] = 'reached'

        elif goal.end_date and fields.date.today() > goal.end_date:
            # check goal failure
            result[goal.id]['state'] = 'failed'
            result[goal.id]['closed'] = True

        return result

    def update(self, cr, uid, ids, context=None):
        """Update the goals to recomputes values and change of states

        If a manual goal is not updated for enough time, the user will be
        reminded to do so (done only once, in 'inprogress' state).
        If a goal reaches the target value, the status is set to reached
        If the end date is passed (at least +1 day, time not considered) without
        the target value being reached, the goal is set as failed."""
        if context is None:
            context = {}
        commit = context.get('commit_gamification', False)

        goals_by_definition = {}
        for goal in self.browse(cr, uid, ids, context=context):
            goals_by_definition.setdefault(goal.definition_id, []).append(goal)

        for definition, goals in goals_by_definition.items():
            goals_to_write = dict((goal.id, {}) for goal in goals)
            if definition.computation_mode == 'manually':
                for goal in goals:
                    goals_to_write[goal.id].update(
                        self._check_remind_delay(cr, uid, goal, context))
            elif definition.computation_mode == 'python':
                # TODO batch execution
                for goal in goals:
                    # execute the chosen method
                    cxt = {
                        'self': self.pool.get('gamification.goal'),
                        'object': goal,
                        'pool': self.pool,
                        'cr': cr,
                        'context':
                        dict(context
                             ),  # copy context to prevent side-effects of eval
                        'uid': uid,
                        'date': date,
                        'datetime': datetime,
                        'timedelta': timedelta,
                        'time': time
                    }
                    code = definition.compute_code.strip()
                    safe_eval(code, cxt, mode="exec", nocopy=True)
                    # the result of the evaluated codeis put in the 'result' local variable, propagated to the context
                    result = cxt.get('result')
                    if result is not None and type(result) in (float, int,
                                                               long):
                        goals_to_write.update(
                            self._get_write_values(cr,
                                                   uid,
                                                   goal,
                                                   result,
                                                   context=context))

                    else:
                        _logger.exception(
                            _('Invalid return content from the evaluation of code for definition %s'
                              ) % definition.name)

            else:  # count or sum

                obj = self.pool.get(definition.model_id.model)
                field_date_name = definition.field_date_id and definition.field_date_id.name or False

                if definition.computation_mode == 'count' and definition.batch_mode:
                    # batch mode, trying to do as much as possible in one request
                    general_domain = safe_eval(definition.domain)
                    field_name = definition.batch_distinctive_field.name
                    subqueries = {}
                    for goal in goals:
                        start_date = field_date_name and goal.start_date or False
                        end_date = field_date_name and goal.end_date or False
                        subqueries.setdefault(
                            (start_date, end_date), {}).update({
                                goal.id:
                                safe_eval(definition.batch_user_expression,
                                          {'user': goal.user_id})
                            })

                    # the global query should be split by time periods (especially for recurrent goals)
                    for (start_date,
                         end_date), query_goals in subqueries.items():
                        subquery_domain = list(general_domain)
                        subquery_domain.append(
                            (field_name, 'in',
                             list(set(query_goals.values()))))
                        if start_date:
                            subquery_domain.append(
                                (field_date_name, '>=', start_date))
                        if end_date:
                            subquery_domain.append(
                                (field_date_name, '<=', end_date))

                        if field_name == 'id':
                            # grouping on id does not work and is similar to search anyway
                            user_ids = obj.search(cr,
                                                  uid,
                                                  subquery_domain,
                                                  context=context)
                            user_values = [{
                                'id': user_id,
                                'id_count': 1
                            } for user_id in user_ids]
                        else:
                            user_values = obj.read_group(cr,
                                                         uid,
                                                         subquery_domain,
                                                         fields=[field_name],
                                                         groupby=[field_name],
                                                         context=context)
                        # user_values has format of read_group: [{'partner_id': 42, 'partner_id_count': 3},...]
                        for goal in [
                                g for g in goals if g.id in query_goals.keys()
                        ]:
                            for user_value in user_values:
                                queried_value = field_name in user_value and user_value[
                                    field_name] or False
                                if isinstance(queried_value, tuple) and len(
                                        queried_value) == 2 and isinstance(
                                            queried_value[0], (int, long)):
                                    queried_value = queried_value[0]
                                if queried_value == query_goals[goal.id]:
                                    new_value = user_value.get(
                                        field_name + '_count', goal.current)
                                    goals_to_write.update(
                                        self._get_write_values(
                                            cr,
                                            uid,
                                            goal,
                                            new_value,
                                            context=context))

                else:
                    for goal in goals:
                        # eval the domain with user replaced by goal user object
                        domain = safe_eval(definition.domain,
                                           {'user': goal.user_id})

                        # add temporal clause(s) to the domain if fields are filled on the goal
                        if goal.start_date and field_date_name:
                            domain.append(
                                (field_date_name, '>=', goal.start_date))
                        if goal.end_date and field_date_name:
                            domain.append(
                                (field_date_name, '<=', goal.end_date))

                        if definition.computation_mode == 'sum':
                            field_name = definition.field_id.name
                            # TODO for master: group on user field in batch mode
                            res = obj.read_group(cr,
                                                 uid,
                                                 domain, [field_name], [],
                                                 context=context)
                            new_value = res and res[0][field_name] or 0.0

                        else:  # computation mode = count
                            new_value = obj.search(cr,
                                                   uid,
                                                   domain,
                                                   context=context,
                                                   count=True)

                        goals_to_write.update(
                            self._get_write_values(cr,
                                                   uid,
                                                   goal,
                                                   new_value,
                                                   context=context))

            for goal_id, value in goals_to_write.items():
                if not value:
                    continue
                self.write(cr, uid, [goal_id], value, context=context)
            if commit:
                cr.commit()
        return True

    def action_start(self, cr, uid, ids, context=None):
        """Mark a goal as started.

        This should only be used when creating goals manually (in draft state)"""
        self.write(cr, uid, ids, {'state': 'inprogress'}, context=context)
        return self.update(cr, uid, ids, context=context)

    def action_reach(self, cr, uid, ids, context=None):
        """Mark a goal as reached.

        If the target goal condition is not met, the state will be reset to In
        Progress at the next goal update until the end date."""
        return self.write(cr, uid, ids, {'state': 'reached'}, context=context)

    def action_fail(self, cr, uid, ids, context=None):
        """Set the state of the goal to failed.

        A failed goal will be ignored in future checks."""
        return self.write(cr, uid, ids, {'state': 'failed'}, context=context)

    def action_cancel(self, cr, uid, ids, context=None):
        """Reset the completion after setting a goal as reached or failed.

        This is only the current state, if the date and/or target criterias
        match the conditions for a change of state, this will be applied at the
        next goal update."""
        return self.write(cr,
                          uid,
                          ids, {'state': 'inprogress'},
                          context=context)

    def create(self, cr, uid, vals, context=None):
        """Overwrite the create method to add a 'no_remind_goal' field to True"""
        context = dict(context or {})
        context['no_remind_goal'] = True
        return super(gamification_goal, self).create(cr,
                                                     uid,
                                                     vals,
                                                     context=context)

    def write(self, cr, uid, ids, vals, context=None):
        """Overwrite the write method to update the last_update field to today

        If the current value is changed and the report frequency is set to On
        change, a report is generated
        """
        if context is None:
            context = {}
        vals['last_update'] = fields.date.today()
        result = super(gamification_goal, self).write(cr,
                                                      uid,
                                                      ids,
                                                      vals,
                                                      context=context)
        for goal in self.browse(cr, uid, ids, context=context):
            if goal.state != "draft" and ('definition_id' in vals
                                          or 'user_id' in vals):
                # avoid drag&drop in kanban view
                raise UserError(
                    _('Can not modify the configuration of a started goal'))

            if vals.get('current'):
                if 'no_remind_goal' in context:
                    # new goals should not be reported
                    continue

                if goal.challenge_id and goal.challenge_id.report_message_frequency == 'onchange':
                    self.pool.get('gamification.challenge').report_progress(
                        cr,
                        SUPERUSER_ID,
                        goal.challenge_id,
                        users=[goal.user_id],
                        context=context)
        return result

    def get_action(self, cr, uid, goal_id, context=None):
        """Get the ir.action related to update the goal

        In case of a manual goal, should return a wizard to update the value
        :return: action description in a dictionnary
        """
        goal = self.browse(cr, uid, goal_id, context=context)

        if goal.definition_id.action_id:
            # open a the action linked to the goal
            action = goal.definition_id.action_id.read()[0]

            if goal.definition_id.res_id_field:
                current_user = self.pool.get('res.users').browse(
                    cr, uid, uid, context=context)
                action['res_id'] = safe_eval(goal.definition_id.res_id_field,
                                             {'user': current_user})

                # if one element to display, should see it in form mode if possible
                action['views'] = [(view_id, mode)
                                   for (view_id, mode) in action['views']
                                   if mode == 'form'] or action['views']
            return action

        if goal.computation_mode == 'manually':
            # open a wizard window to update the value manually
            action = {
                'name': _("Update %s") % goal.definition_id.name,
                'id': goal_id,
                'type': 'ir.actions.act_window',
                'views': [[False, 'form']],
                'target': 'new',
                'context': {
                    'default_goal_id': goal_id,
                    'default_current': goal.current
                },
                'res_model': 'gamification.goal.wizard'
            }
            return action

        return False
Example #17
0
class sale_order_line(osv.osv):
    _inherit = 'sale.order.line'
    _columns = {
        'event_id':
        fields.many2one(
            'event.event',
            'Event',
            help=
            "Choose an event and it will automatically create a registration for this event."
        ),
        'event_ticket_id':
        fields.many2one(
            'event.event.ticket',
            'Event Ticket',
            help=
            "Choose an event ticket and it will automatically create a registration for this event ticket."
        ),
        # those 2 fields are used for dynamic domains and filled by onchange
        # TDE: really necessary ? ...
        'event_type_id':
        fields.related('product_id',
                       'event_type_id',
                       type='many2one',
                       relation="event.type",
                       string="Event Type"),
        'event_ok':
        fields.related('product_id',
                       'event_ok',
                       string='event_ok',
                       type='boolean'),
    }

    def _prepare_order_line_invoice_line(self,
                                         cr,
                                         uid,
                                         line,
                                         account_id=False,
                                         context=None):
        res = super(sale_order_line, self)._prepare_order_line_invoice_line(
            cr, uid, line, account_id=account_id, context=context)
        if line.event_id:
            event = self.pool['event.event'].read(cr,
                                                  uid,
                                                  line.event_id.id, ['name'],
                                                  context=context)
            res['name'] = '%s: %s' % (res.get('name', ''), event['name'])
        return res

    @api.onchange('product_id')
    def product_id_change_event(self):
        if self.product_id.event_ok:
            values = dict(event_type_id=self.product_id.event_type_id.id,
                          event_ok=self.product_id.event_ok)
        else:
            values = dict(event_type_id=False, event_ok=False)
        self.update(values)

    @api.multi
    def _update_registrations(self, confirm=True, registration_data=None):
        """ Create or update registrations linked to a sale order line. A sale
        order line has a product_uom_qty attribute that will be the number of
        registrations linked to this line. This method update existing registrations
        and create new one for missing one. """
        Registration = self.env['event.registration']
        registrations = Registration.search([('sale_order_line_id', 'in',
                                              self.ids)])
        for so_line in [l for l in self if l.event_id]:
            existing_registrations = registrations.filtered(
                lambda self: self.sale_order_line_id.id == so_line.id)
            if confirm:
                existing_registrations.filtered(
                    lambda self: self.state != 'open').confirm_registration()
            else:
                existing_registrations.filtered(
                    lambda self: self.state == 'cancel').do_draft()

            for count in range(
                    int(so_line.product_uom_qty) -
                    len(existing_registrations)):
                registration = {}
                if registration_data:
                    registration = registration_data.pop()
                # TDE CHECK: auto confirmation
                registration['sale_order_line_id'] = so_line
                self.env['event.registration'].with_context(
                    registration_force_draft=True).create(
                        Registration._prepare_attendee_values(registration))
        return True

    def onchange_event_ticket_id(self,
                                 cr,
                                 uid,
                                 ids,
                                 event_ticket_id=False,
                                 context=None):
        price = event_ticket_id and self.pool["event.event.ticket"].browse(
            cr, uid, event_ticket_id, context=context).price or False
        return {'value': {'price_unit': price}}
Example #18
0
class campaign_analysis(osv.osv):
    _name = "campaign.analysis"
    _description = "Campaign Analysis"
    _auto = False
    _rec_name = 'date'

    def _total_cost(self, cr, uid, ids, field_name, arg, context=None):
        """
            @param cr: the current row, from the database cursor,
            @param uid: the current user’s ID for security checks,
            @param ids: List of case and section Data’s IDs
            @param context: A standard dictionary for contextual values
        """
        result = {}
        for ca_obj in self.browse(cr, uid, ids, context=context):
            wi_ids = self.pool.get('marketing.campaign.workitem').search(
                cr, uid,
                [('segment_id.campaign_id', '=', ca_obj.campaign_id.id)])
            total_cost = ca_obj.activity_id.variable_cost + \
                                ((ca_obj.campaign_id.fixed_cost or 1.00) / len(wi_ids))
            result[ca_obj.id] = total_cost
        return result

    _columns = {
        'res_id':
        fields.integer('Resource', readonly=True),
        'year':
        fields.char('Execution Year', size=4, readonly=True),
        'month':
        fields.selection([('01', 'January'), ('02', 'February'),
                          ('03', 'March'), ('04', 'April'), ('05', 'May'),
                          ('06', 'June'), ('07', 'July'), ('08', 'August'),
                          ('09', 'September'), ('10', 'October'),
                          ('11', 'November'), ('12', 'December')],
                         'Execution Month',
                         readonly=True),
        'day':
        fields.char('Execution Day', size=10, readonly=True),
        'date':
        fields.date('Execution Date', readonly=True, select=True),
        'campaign_id':
        fields.many2one('marketing.campaign', 'Campaign', readonly=True),
        'activity_id':
        fields.many2one('marketing.campaign.activity',
                        'Activity',
                        readonly=True),
        'segment_id':
        fields.many2one('marketing.campaign.segment', 'Segment',
                        readonly=True),
        'partner_id':
        fields.many2one('res.partner', 'Partner', readonly=True),
        'country_id':
        fields.related('partner_id',
                       'country_id',
                       type='many2one',
                       relation='res.country',
                       string='Country'),
        'total_cost':
        fields.function(_total_cost, string='Cost', type="float"),
        'revenue':
        fields.float('Revenue', readonly=True, digits=0),
        'count':
        fields.integer('# of Actions', readonly=True),
        'state':
        fields.selection([('todo', 'To Do'), ('exception', 'Exception'),
                          ('done', 'Done'), ('cancelled', 'Cancelled')],
                         'Status',
                         readonly=True),
    }

    def init(self, cr):
        tools.drop_view_if_exists(cr, 'campaign_analysis')
        cr.execute("""
            create or replace view campaign_analysis as (
            select
                min(wi.id) as id,
                min(wi.res_id) as res_id,
                to_char(wi.date::date, 'YYYY') as year,
                to_char(wi.date::date, 'MM') as month,
                to_char(wi.date::date, 'YYYY-MM-DD') as day,
                wi.date::date as date,
                s.campaign_id as campaign_id,
                wi.activity_id as activity_id,
                wi.segment_id as segment_id,
                wi.partner_id as partner_id ,
                wi.state as state,
                sum(act.revenue) as revenue,
                count(*) as count
            from
                marketing_campaign_workitem wi
                left join res_partner p on (p.id=wi.partner_id)
                left join marketing_campaign_segment s on (s.id=wi.segment_id)
                left join marketing_campaign_activity act on (act.id= wi.activity_id)
            group by
                s.campaign_id,wi.activity_id,wi.segment_id,wi.partner_id,wi.state,
                wi.date::date
            )
        """)
Example #19
0
class gamification_goal_definition(osv.Model):
    """Goal definition

    A goal definition contains the way to evaluate an objective
    Each module wanting to be able to set goals to the users needs to create
    a new gamification_goal_definition
    """
    _name = 'gamification.goal.definition'
    _description = 'Gamification goal definition'

    def _get_suffix(self, cr, uid, ids, field_name, arg, context=None):
        res = dict.fromkeys(ids, '')
        for goal in self.browse(cr, uid, ids, context=context):
            if goal.suffix and not goal.monetary:
                res[goal.id] = goal.suffix
            elif goal.monetary:
                # use the current user's company currency
                user = self.pool.get('res.users').browse(cr, uid, uid, context)
                if goal.suffix:
                    res[goal.id] = "%s %s" % (
                        user.company_id.currency_id.symbol, goal.suffix)
                else:
                    res[goal.id] = user.company_id.currency_id.symbol
            else:
                res[goal.id] = ""
        return res

    _columns = {
        'name':
        fields.char('Goal Definition', required=True, translate=True),
        'description':
        fields.text('Goal Description'),
        'monetary':
        fields.boolean(
            'Monetary Value',
            help=
            "The target and current value are defined in the company currency."
        ),
        'suffix':
        fields.char('Suffix',
                    help="The unit of the target and current values",
                    translate=True),
        'full_suffix':
        fields.function(_get_suffix,
                        type="char",
                        string="Full Suffix",
                        help="The currency and suffix field"),
        'computation_mode':
        fields.selection(
            [
                ('manually', 'Recorded manually'),
                ('count', 'Automatic: number of records'),
                ('sum', 'Automatic: sum on a field'),
                ('python', 'Automatic: execute a specific Python code'),
            ],
            string="Computation Mode",
            help=
            "Defined how will be computed the goals. The result of the operation will be stored in the field 'Current'.",
            required=True),
        'display_mode':
        fields.selection([
            ('progress', 'Progressive (using numerical values)'),
            ('boolean', 'Exclusive (done or not-done)'),
        ],
                         string="Displayed as",
                         required=True),
        'model_id':
        fields.many2one('ir.model',
                        string='Model',
                        help='The model object for the field to evaluate'),
        'model_inherited_model_ids':
        fields.related('model_id',
                       'inherited_model_ids',
                       type="many2many",
                       obj="ir.model",
                       string="Inherited models",
                       readonly="True"),
        'field_id':
        fields.many2one('ir.model.fields',
                        string='Field to Sum',
                        help='The field containing the value to evaluate'),
        'field_date_id':
        fields.many2one('ir.model.fields',
                        string='Date Field',
                        help='The date to use for the time period evaluated'),
        'domain':
        fields.char(
            "Filter Domain",
            help=
            "Domain for filtering records. General rule, not user depending, e.g. [('state', '=', 'done')]. The expression can contain reference to 'user' which is a browse record of the current user if not in batch mode.",
            required=True),
        'batch_mode':
        fields.boolean(
            'Batch Mode',
            help=
            "Evaluate the expression in batch instead of once for each user"),
        'batch_distinctive_field':
        fields.many2one(
            'ir.model.fields',
            string="Distinctive field for batch user",
            help=
            "In batch mode, this indicates which field distinct one user form the other, e.g. user_id, partner_id..."
        ),
        'batch_user_expression':
        fields.char(
            "Evaluted expression for batch mode",
            help=
            "The value to compare with the distinctive field. The expression can contain reference to 'user' which is a browse record of the current user, e.g. user.id, user.partner_id.id..."
        ),
        'compute_code':
        fields.text(
            'Python Code',
            help=
            "Python code to be executed for each user. 'result' should contains the new current value. Evaluated user can be access through object.user_id."
        ),
        'condition':
        fields.selection(
            [('higher', 'The higher the better'),
             ('lower', 'The lower the better')],
            string='Goal Performance',
            help=
            'A goal is considered as completed when the current value is compared to the value to reach',
            required=True),
        'action_id':
        fields.many2one(
            'ir.actions.act_window',
            string="Action",
            help="The action that will be called to update the goal value."),
        'res_id_field':
        fields.char(
            "ID Field of user",
            help=
            "The field name on the user profile (res.users) containing the value for res_id for action."
        ),
    }

    _defaults = {
        'condition': 'higher',
        'computation_mode': 'manually',
        'domain': "[]",
        'monetary': False,
        'display_mode': 'progress',
    }

    def number_following(self,
                         cr,
                         uid,
                         model_name="mail.thread",
                         context=None):
        """Return the number of 'model_name' objects the user is following

        The model specified in 'model_name' must inherit from mail.thread
        """
        user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
        return self.pool.get('mail.followers').search(
            cr,
            uid, [('res_model', '=', model_name),
                  ('partner_id', '=', user.partner_id.id)],
            count=True,
            context=context)

    def _check_domain_validity(self, cr, uid, ids, context=None):
        # take admin as should always be present
        superuser = self.pool['res.users'].browse(cr,
                                                  uid,
                                                  SUPERUSER_ID,
                                                  context=context)
        for definition in self.browse(cr, uid, ids, context=context):
            if definition.computation_mode not in ('count', 'sum'):
                continue

            obj = self.pool[definition.model_id.model]
            try:
                domain = safe_eval(definition.domain, {'user': superuser})
                # demmy search to make sure the domain is valid
                obj.search(cr, uid, domain, context=context, count=True)
            except (ValueError, SyntaxError), e:
                msg = e.message or (e.msg + '\n' + e.text)
                raise UserError(
                    _("The domain for the definition %s seems incorrect, please check it.\n\n%s"
                      % (definition.name, msg)))
        return True
Example #20
0
class marketing_campaign_activity(osv.osv):
    _name = "marketing.campaign.activity"
    _order = "name"
    _description = "Campaign Activity"

    _action_types = [
        ('email', 'Email'),
        ('report', 'Report'),
        ('action', 'Custom Action'),
        # TODO implement the subcampaigns.
        # TODO implement the subcampaign out. disallow out transitions from
        # subcampaign activities ?
        #('subcampaign', 'Sub-Campaign'),
    ]

    _columns = {
        'name': fields.char('Name', required=True),
        'campaign_id': fields.many2one('marketing.campaign', 'Campaign',
                                            required = True, ondelete='cascade', select=1),
        'object_id': fields.related('campaign_id','object_id',
                                      type='many2one', relation='ir.model',
                                      string='Object', readonly=True),
        'start': fields.boolean('Start', help= "This activity is launched when the campaign starts.", select=True),
        'condition': fields.text('Condition', size=256, required=True,
                                 help="Python expression to decide whether the activity can be executed, otherwise it will be deleted or cancelled."
                                 "The expression may use the following [browsable] variables:\n"
                                 "   - activity: the campaign activity\n"
                                 "   - workitem: the campaign workitem\n"
                                 "   - resource: the resource object this campaign item represents\n"
                                 "   - transitions: list of campaign transitions outgoing from this activity\n"
                                 "...- re: Python regular expression module"),
        'type': fields.selection(_action_types, 'Type', required=True,
                                  help="""The type of action to execute when an item enters this activity, such as:
   - Email: send an email using a predefined email template
   - Report: print an existing Report defined on the resource item and save it into a specific directory
   - Custom Action: execute a predefined action, e.g. to modify the fields of the resource record
  """),
        'email_template_id': fields.many2one('mail.template', "Email Template", help='The email to send when this activity is activated'),
        'report_id': fields.many2one('ir.actions.report.xml', "Report", help='The report to generate when this activity is activated', ),
        'server_action_id': fields.many2one('ir.actions.server', string='Action',
                                help= "The action to perform when this activity is activated"),
        'to_ids': fields.one2many('marketing.campaign.transition',
                                            'activity_from_id',
                                            'Next Activities'),
        'from_ids': fields.one2many('marketing.campaign.transition',
                                            'activity_to_id',
                                            'Previous Activities'),
        'variable_cost': fields.float('Variable Cost', help="Set a variable cost if you consider that every campaign item that has reached this point has entailed a certain cost. You can get cost statistics in the Reporting section", digits_compute=dp.get_precision('Product Price')),
        'revenue': fields.float('Revenue', help="Set an expected revenue if you consider that every campaign item that has reached this point has generated a certain revenue. You can get revenue statistics in the Reporting section", digits=0),
        'signal': fields.char('Signal', 
                              help='An activity with a signal can be called programmatically. Be careful, the workitem is always created when a signal is sent'),
        'keep_if_condition_not_met': fields.boolean("Don't Delete Workitems",
                                                    help="By activating this option, workitems that aren't executed because the condition is not met are marked as cancelled instead of being deleted.")
    }

    _defaults = {
        'type': lambda *a: 'email',
        'condition': lambda *a: 'True',
    }

    def search(self, cr, uid, args, offset=0, limit=None, order=None,
                                        context=None, count=False):
        if context == None:
            context = {}
        if 'segment_id' in context  and context['segment_id']:
            segment_obj = self.pool.get('marketing.campaign.segment').browse(cr,
                                                    uid, context['segment_id'])
            act_ids = []
            for activity in segment_obj.campaign_id.activity_ids:
                act_ids.append(activity.id)
            return act_ids
        return super(marketing_campaign_activity, self).search(cr, uid, args,
                                           offset, limit, order, context, count)

    #dead code
    def _process_wi_report(self, cr, uid, activity, workitem, context=None):
        report_data, format = render_report(cr, uid, [], activity.report_id.report_name, {}, context=context)
        attach_vals = {
            'name': '%s_%s_%s'%(activity.report_id.report_name,
                                activity.name,workitem.partner_id.name),
            'datas_fname': '%s.%s'%(activity.report_id.report_name,
                                        activity.report_id.report_type),
            'datas': base64.encodestring(report_data),
        }
        self.pool.get('ir.attachment').create(cr, uid, attach_vals)
        return True

    def _process_wi_email(self, cr, uid, activity, workitem, context=None):
        return self.pool.get('mail.template').send_mail(cr, uid,
                                            activity.email_template_id.id,
                                            workitem.res_id, context=context)

    #dead code
    def _process_wi_action(self, cr, uid, activity, workitem, context=None):
        if context is None:
            context = {}
        server_obj = self.pool.get('ir.actions.server')

        action_context = dict(context,
                              active_id=workitem.res_id,
                              active_ids=[workitem.res_id],
                              active_model=workitem.object_id.model,
                              workitem=workitem)
        server_obj.run(cr, uid, [activity.server_action_id.id],
                             context=action_context)
        return True

    def process(self, cr, uid, act_id, wi_id, context=None):
        activity = self.browse(cr, uid, act_id, context=context)
        method = '_process_wi_%s' % (activity.type,)
        action = getattr(self, method, None)
        if not action:
            raise NotImplementedError('Method %r is not implemented on %r object.' % (method, self))

        workitem_obj = self.pool.get('marketing.campaign.workitem')
        workitem = workitem_obj.browse(cr, uid, wi_id, context=context)
        return action(cr, uid, activity, workitem, context=context)
Example #21
0
class website_config_settings(osv.osv_memory):
    _name = 'website.config.settings'
    _inherit = 'res.config.settings'

    _columns = {
        'website_id':
        fields.many2one('website', string="website", required=True),
        'website_name':
        fields.related('website_id',
                       'name',
                       type="char",
                       string="Website Name"),
        'language_ids':
        fields.related('website_id',
                       'language_ids',
                       type='many2many',
                       relation='res.lang',
                       string='Languages'),
        'default_lang_id':
        fields.related('website_id',
                       'default_lang_id',
                       type='many2one',
                       relation='res.lang',
                       string='Default language'),
        'default_lang_code':
        fields.related('website_id',
                       'default_lang_code',
                       type="char",
                       string="Default language code"),
        'google_analytics_key':
        fields.related('website_id',
                       'google_analytics_key',
                       type="char",
                       string='Google Analytics Key'),
        'social_twitter':
        fields.related('website_id',
                       'social_twitter',
                       type="char",
                       string='Twitter Account'),
        'social_facebook':
        fields.related('website_id',
                       'social_facebook',
                       type="char",
                       string='Facebook Account'),
        'social_github':
        fields.related('website_id',
                       'social_github',
                       type="char",
                       string='GitHub Account'),
        'social_linkedin':
        fields.related('website_id',
                       'social_linkedin',
                       type="char",
                       string='LinkedIn Account'),
        'social_youtube':
        fields.related('website_id',
                       'social_youtube',
                       type="char",
                       string='Youtube Account'),
        'social_googleplus':
        fields.related('website_id',
                       'social_googleplus',
                       type="char",
                       string='Google+ Account'),
        'compress_html':
        fields.related(
            'website_id',
            'compress_html',
            type="boolean",
            string='Compress rendered HTML for a better Google PageSpeed result'
        ),
        'cdn_activated':
        fields.related('website_id',
                       'cdn_activated',
                       type="boolean",
                       string='Use a Content Delivery Network (CDN)'),
        'cdn_url':
        fields.related('website_id',
                       'cdn_url',
                       type="char",
                       string='CDN Base URL'),
        'cdn_filters':
        fields.related('website_id',
                       'cdn_filters',
                       type="text",
                       string='CDN Filters'),
        'module_website_form_editor':
        fields.boolean("Form builder: create and customize forms"),
        'module_website_version':
        fields.boolean("A/B testing and versioning"),
    }

    def on_change_website_id(self, cr, uid, ids, website_id, context=None):
        if not website_id:
            return {'value': {}}
        website_data = self.pool.get('website').read(cr,
                                                     uid, [website_id], [],
                                                     context=context)[0]
        values = {'website_name': website_data['name']}
        for fname, v in website_data.items():
            if fname in self._columns:
                values[fname] = v[
                    0] if v and self._columns[fname]._type == 'many2one' else v
        return {'value': values}

    # FIXME in trunk for god sake. Change the fields above to fields.char instead of fields.related,
    # and create the function set_website who will set the value on the website_id
    # create does not forward the values to the related many2one. Write does.
    def create(self, cr, uid, vals, context=None):
        config_id = super(website_config_settings,
                          self).create(cr, uid, vals, context=context)
        self.write(cr, uid, config_id, vals, context=context)
        return config_id

    _defaults = {
        'website_id':
        lambda self, cr, uid, c: self.pool.get('website').search(
            cr, uid, [], context=c)[0],
    }
Example #22
0
class config(osv.Model):
    _name = 'google.drive.config'
    _description = "Google Drive templates config"

    def get_google_drive_url(self,
                             cr,
                             uid,
                             config_id,
                             res_id,
                             template_id,
                             context=None):
        config = self.browse(cr, SUPERUSER_ID, config_id, context=context)
        model = config.model_id
        filter_name = config.filter_id and config.filter_id.name or False
        record = self.pool.get(model.model).read(cr,
                                                 uid, [res_id],
                                                 context=context)[0]
        record.update({'model': model.name, 'filter': filter_name})
        name_gdocs = config.name_template
        try:
            name_gdocs = name_gdocs % record
        except:
            raise UserError(
                _("At least one key cannot be found in your Google Drive name pattern"
                  ))

        attach_pool = self.pool.get("ir.attachment")
        attach_ids = attach_pool.search(cr, uid,
                                        [('res_model', '=', model.model),
                                         ('name', '=', name_gdocs),
                                         ('res_id', '=', res_id)])
        url = False
        if attach_ids:
            attachment = attach_pool.browse(cr, uid, attach_ids[0], context)
            url = attachment.url
        else:
            url = self.copy_doc(cr, uid, res_id, template_id, name_gdocs,
                                model.model, context).get('url')
        return url

    def get_access_token(self, cr, uid, scope=None, context=None):
        ir_config = self.pool['ir.config_parameter']
        google_drive_refresh_token = ir_config.get_param(
            cr, SUPERUSER_ID, 'google_drive_refresh_token')
        if not google_drive_refresh_token:
            if self.pool['res.users']._is_admin(cr, uid, [uid]):
                model, action_id = self.pool[
                    'ir.model.data'].get_object_reference(
                        cr, uid, 'base_setup', 'action_general_configuration')
                msg = _(
                    "You haven't configured 'Authorization Code' generated from google, Please generate and configure it ."
                )
                raise yuancloud.exceptions.RedirectWarning(
                    msg, action_id, _('Go to the configuration panel'))
            else:
                raise UserError(
                    _("Google Drive is not yet configured. Please contact your administrator."
                      ))
        google_drive_client_id = ir_config.get_param(cr, SUPERUSER_ID,
                                                     'google_drive_client_id')
        google_drive_client_secret = ir_config.get_param(
            cr, SUPERUSER_ID, 'google_drive_client_secret')
        #For Getting New Access Token With help of old Refresh Token

        data = werkzeug.url_encode(
            dict(client_id=google_drive_client_id,
                 refresh_token=google_drive_refresh_token,
                 client_secret=google_drive_client_secret,
                 grant_type="refresh_token",
                 scope=scope or 'https://www.googleapis.com/auth/drive'))
        headers = {"Content-type": "application/x-www-form-urlencoded"}
        try:
            req = urllib2.Request('https://accounts.google.com/o/oauth2/token',
                                  data, headers)
            content = urllib2.urlopen(req, timeout=TIMEOUT).read()
        except urllib2.HTTPError:
            if user_is_admin:
                model, action_id = self.pool[
                    'ir.model.data'].get_object_reference(
                        cr, uid, 'base_setup', 'action_general_configuration')
                msg = _(
                    "Something went wrong during the token generation. Please request again an authorization code ."
                )
                raise yuancloud.exceptions.RedirectWarning(
                    msg, action_id, _('Go to the configuration panel'))
            else:
                raise UserError(
                    _("Google Drive is not yet configured. Please contact your administrator."
                      ))
        content = json.loads(content)
        return content.get('access_token')

    def copy_doc(self,
                 cr,
                 uid,
                 res_id,
                 template_id,
                 name_gdocs,
                 res_model,
                 context=None):
        ir_config = self.pool['ir.config_parameter']
        google_web_base_url = ir_config.get_param(cr, SUPERUSER_ID,
                                                  'web.base.url')
        access_token = self.get_access_token(cr, uid, context=context)
        # Copy template in to drive with help of new access token
        request_url = "https://www.googleapis.com/drive/v2/files/%s?fields=parents/id&access_token=%s" % (
            template_id, access_token)
        headers = {"Content-type": "application/x-www-form-urlencoded"}
        try:
            req = urllib2.Request(request_url, None, headers)
            parents = urllib2.urlopen(req, timeout=TIMEOUT).read()
        except urllib2.HTTPError:
            raise UserError(
                _("The Google Template cannot be found. Maybe it has been deleted."
                  ))
        parents_dict = json.loads(parents)

        record_url = "Click on link to open Record in YuanCloud\n %s/?db=%s#id=%s&model=%s" % (
            google_web_base_url, cr.dbname, res_id, res_model)
        data = {
            "title": name_gdocs,
            "description": record_url,
            "parents": parents_dict['parents']
        }
        request_url = "https://www.googleapis.com/drive/v2/files/%s/copy?access_token=%s" % (
            template_id, access_token)
        headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
        data_json = json.dumps(data)
        # resp, content = Http().request(request_url, "POST", data_json, headers)
        req = urllib2.Request(request_url, data_json, headers)
        content = urllib2.urlopen(req, timeout=TIMEOUT).read()
        content = json.loads(content)
        res = {}
        if content.get('alternateLink'):
            attach_pool = self.pool.get("ir.attachment")
            attach_vals = {
                'res_model': res_model,
                'name': name_gdocs,
                'res_id': res_id,
                'type': 'url',
                'url': content['alternateLink']
            }
            res['id'] = attach_pool.create(cr, uid, attach_vals)
            # Commit in order to attach the document to the current object instance, even if the permissions has not been written.
            cr.commit()
            res['url'] = content['alternateLink']
            key = self._get_key_from_url(res['url'])
            request_url = "https://www.googleapis.com/drive/v2/files/%s/permissions?emailMessage=This+is+a+drive+file+created+by+YuanCloud&sendNotificationEmails=false&access_token=%s" % (
                key, access_token)
            data = {
                'role': 'writer',
                'type': 'anyone',
                'value': '',
                'withLink': True
            }
            try:
                req = urllib2.Request(request_url, json.dumps(data), headers)
                urllib2.urlopen(req, timeout=TIMEOUT)
            except urllib2.HTTPError:
                raise self.pool.get('res.config.settings').get_config_warning(
                    cr,
                    _("The permission 'reader' for 'anyone with the link' has not been written on the document"
                      ),
                    context=context)
            user = self.pool['res.users'].browse(cr, uid, uid, context=context)
            if user.email:
                data = {'role': 'writer', 'type': 'user', 'value': user.email}
                try:
                    req = urllib2.Request(request_url, json.dumps(data),
                                          headers)
                    urllib2.urlopen(req, timeout=TIMEOUT)
                except urllib2.HTTPError:
                    pass
        return res

    def get_google_drive_config(self,
                                cr,
                                uid,
                                res_model,
                                res_id,
                                context=None):
        '''
        Function called by the js, when no google doc are yet associated with a record, with the aim to create one. It
        will first seek for a google.docs.config associated with the model `res_model` to find out what's the template
        of google doc to copy (this is usefull if you want to start with a non-empty document, a type or a name
        different than the default values). If no config is associated with the `res_model`, then a blank text document
        with a default name is created.
          :param res_model: the object for which the google doc is created
          :param ids: the list of ids of the objects for which the google doc is created. This list is supposed to have
            a length of 1 element only (batch processing is not supported in the code, though nothing really prevent it)
          :return: the config id and config name
        '''
        if not res_id:
            raise UserError(
                _("Creating google drive may only be done by one at a time."))
        # check if a model is configured with a template
        config_ids = self.search(cr,
                                 uid, [('model_id', '=', res_model)],
                                 context=context)
        configs = []
        for config in self.browse(cr, uid, config_ids, context=context):
            if config.filter_id:
                if (config.filter_id.user_id
                        and config.filter_id.user_id.id != uid):
                    #Private
                    continue
                domain = [('id', 'in', [res_id])] + eval(
                    config.filter_id.domain)
                local_context = context and context.copy() or {}
                local_context.update(eval(config.filter_id.context))
                google_doc_configs = self.pool.get(
                    config.filter_id.model_id).search(cr,
                                                      uid,
                                                      domain,
                                                      context=local_context)
                if google_doc_configs:
                    configs.append({'id': config.id, 'name': config.name})
            else:
                configs.append({'id': config.id, 'name': config.name})
        return configs

    def _get_key_from_url(self, url):
        mo = re.search("(key=|/d/)([A-Za-z0-9-_]+)", url)
        if mo:
            return mo.group(2)
        return None

    def _resource_get(self, cr, uid, ids, name, arg, context=None):
        result = {}
        for data in self.browse(cr, uid, ids, context):
            mo = self._get_key_from_url(data.google_drive_template_url)
            if mo:
                result[data.id] = mo
            else:
                raise UserError(_("Please enter a valid Google Document URL."))
        return result

    def _client_id_get(self, cr, uid, ids, name, arg, context=None):
        result = {}
        client_id = self.pool['ir.config_parameter'].get_param(
            cr, SUPERUSER_ID, 'google_drive_client_id')
        for config_id in ids:
            result[config_id] = client_id
        return result

    _columns = {
        'name':
        fields.char('Template Name', required=True),
        'model_id':
        fields.many2one('ir.model',
                        'Model',
                        ondelete='set null',
                        required=True),
        'model':
        fields.related('model_id',
                       'model',
                       type='char',
                       string='Model',
                       readonly=True),
        'filter_id':
        fields.many2one('ir.filters',
                        'Filter',
                        domain="[('model_id', '=', model)]"),
        'google_drive_template_url':
        fields.char('Template URL', required=True, size=1024),
        'google_drive_resource_id':
        fields.function(_resource_get, type="char", string='Resource Id'),
        'google_drive_client_id':
        fields.function(_client_id_get, type="char", string='Google Client '),
        'name_template':
        fields.char(
            'Google Drive Name Pattern',
            help=
            'Choose how the new google drive will be named, on google side. Eg. gdoc_%(field_name)s',
            required=True),
        'active':
        fields.boolean('Active'),
    }

    def onchange_model_id(self, cr, uid, ids, model_id, context=None):
        res = {}
        if model_id:
            model = self.pool['ir.model'].browse(cr,
                                                 uid,
                                                 model_id,
                                                 context=context)
            res['value'] = {'model': model.model}
        else:
            res['value'] = {'filter_id': False, 'model': False}
        return res

    _defaults = {
        'name_template': 'Document %(name)s',
        'active': True,
    }

    def _check_model_id(self, cr, uid, ids, context=None):
        config_id = self.browse(cr, uid, ids[0], context=context)
        if config_id.filter_id and config_id.model_id.model != config_id.filter_id.model_id:
            return False
        return True

    _constraints = [
        (_check_model_id,
         'Model of selected filter is not matching with model of current template.',
         ['model_id', 'filter_id']),
    ]

    def get_google_scope(self):
        return 'https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file'
Example #23
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, ids, 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, ids[0], 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
Example #24
0
class mrp_product_produce(osv.osv_memory):
    _name = "mrp.product.produce"
    _description = "Product Produce"

    _columns = {
        'product_id':
        fields.many2one('product.product', type='many2one'),
        'product_qty':
        fields.float(
            'Select Quantity',
            digits_compute=dp.get_precision('Product Unit of Measure'),
            required=True),
        'mode':
        fields.selection(
            [('consume_produce', 'Consume & Produce'),
             ('consume', 'Consume Only')],
            'Mode',
            required=True,
            help=
            "'Consume only' mode will only consume the products with the quantity selected.\n"
            "'Consume & Produce' mode will consume as well as produce the products with the quantity selected "
            "and it will finish the production order when total ordered quantities are produced."
        ),
        'lot_id':
        fields.many2one(
            'stock.production.lot', 'Lot'
        ),  #Should only be visible when it is consume and produce mode
        'consume_lines':
        fields.one2many('mrp.product.produce.line', 'produce_id',
                        'Products Consumed'),
        'tracking':
        fields.related('product_id',
                       'tracking',
                       type='selection',
                       selection=[('serial', 'By Unique Serial Number'),
                                  ('lot', 'By Lots'),
                                  ('none', 'No Tracking')]),
    }

    def on_change_qty(self,
                      cr,
                      uid,
                      ids,
                      product_qty,
                      consume_lines,
                      context=None):
        """ 
            When changing the quantity of products to be produced it will 
            recalculate the number of raw materials needed according
            to the scheduled products and the already consumed/produced products
            It will return the consume lines needed for the products to be produced
            which the user can still adapt
        """
        prod_obj = self.pool.get("mrp.production")
        uom_obj = self.pool.get("product.uom")
        production = prod_obj.browse(cr,
                                     uid,
                                     context['active_id'],
                                     context=context)
        consume_lines = []
        new_consume_lines = []
        if product_qty > 0.0:
            product_uom_qty = uom_obj._compute_qty(
                cr, uid, production.product_uom.id, product_qty,
                production.product_id.uom_id.id)
            consume_lines = prod_obj._calculate_qty(
                cr,
                uid,
                production,
                product_qty=product_uom_qty,
                context=context)

        for consume in consume_lines:
            new_consume_lines.append([0, False, consume])
        return {'value': {'consume_lines': new_consume_lines}}

    def _get_product_qty(self, cr, uid, context=None):
        """ To obtain product quantity
        @param self: The object pointer.
        @param cr: A database cursor
        @param uid: ID of the user currently logged in
        @param context: A standard dictionary
        @return: Quantity
        """
        if context is None:
            context = {}
        prod = self.pool.get('mrp.production').browse(cr,
                                                      uid,
                                                      context['active_id'],
                                                      context=context)
        done = 0.0
        for move in prod.move_created_ids2:
            if move.product_id == prod.product_id:
                if not move.scrapped:
                    done += move.product_uom_qty  # As uom of produced products and production order should correspond
        return prod.product_qty - done

    def _get_product_id(self, cr, uid, context=None):
        """ To obtain product id
        @return: id
        """
        prod = False
        if context and context.get("active_id"):
            prod = self.pool.get('mrp.production').browse(cr,
                                                          uid,
                                                          context['active_id'],
                                                          context=context)
        return prod and prod.product_id.id or False

    def _get_track(self, cr, uid, context=None):
        prod = self._get_product_id(cr, uid, context=context)
        prod_obj = self.pool.get("product.product")
        return prod and prod_obj.browse(cr, uid, prod,
                                        context=context).tracking or 'none'

    _defaults = {
        'product_qty': _get_product_qty,
        'mode': lambda *x: 'consume_produce',
        'product_id': _get_product_id,
        'tracking': _get_track,
    }

    def do_produce(self, cr, uid, ids, context=None):
        production_id = context.get('active_id', False)
        assert production_id, "Production Id should be specified in context as a Active ID."
        data = self.browse(cr, uid, ids[0], context=context)
        self.pool.get('mrp.production').action_produce(cr,
                                                       uid,
                                                       production_id,
                                                       data.product_qty,
                                                       data.mode,
                                                       data,
                                                       context=context)
        return {}
Example #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'):
                res.append((model._name, getattr(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'
            record_ids = model.search(cr,
                                      uid, [('id', 'in', res_ids),
                                            (email_fname, 'ilike', email)],
                                      context=context)
            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)]"
        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)
Example #26
0
class website(orm.Model):
    _inherit = 'website'

    def _get_pricelist_id(self, cr, uid, ids, name, args, context=None):
        res = {}
        pricelist = self.get_current_pricelist(cr, uid, context=context)
        for data in self.browse(cr, uid, ids, context=context):
            res[data.id] = pricelist.id
        return res

    _columns = {
        'pricelist_id': fields.function(_get_pricelist_id,\
            type='many2one', relation="product.pricelist", string='Default Pricelist'),
        'currency_id': fields.related(
            'pricelist_id', 'currency_id',
            type='many2one', relation='res.currency', string='Default Currency'),
        'salesperson_id': fields.many2one('res.users', 'Salesperson'),
        'salesteam_id': fields.many2one('crm.team', 'Sales Team'),
        'website_pricelist_ids': fields.one2many('website_pricelist', 'website_id',
                                                 string='Price list available for this Ecommerce/Website'),
    }

    @tools.ormcache('uid', 'country_code', 'show_visible', 'website_pl', 'current_pl', 'all_pl')
    def _get_pl(self, cr, uid, country_code, show_visible, website_pl, current_pl, all_pl):
        """ Return the list of pricelists that can be used on website for the current user.

        :param str country_code: code iso or False, If set, we search only price list available for this country
        :param bool show_visible: if True, we don't display pricelist where selectable is False (Eg: Code promo)
        :param int website_pl: The default pricelist used on this website
        :param int current_pl: The current pricelist used on the website
                               (If not selectable but the current pricelist we had this pricelist anyway)
        :param list all_pl: List of all pricelist available for this website

        :returns: list of pricelist ids
        """
        pcs = []

        if country_code:
            groups = self.pool['res.country.group'].search(cr, uid, [('country_ids.code', '=', country_code)])
            for cgroup in self.pool['res.country.group'].browse(cr, uid, groups):
                for pll in cgroup.website_pricelist_ids:
                    if not show_visible or pll.selectable or pll.pricelist_id.id == current_pl:
                        pcs.append(pll.pricelist_id)

        if not pcs:  # no pricelist for this country, or no GeoIP
            pcs = [pll.pricelist_id for pll in all_pl
                   if not show_visible or pll.selectable or pll.pricelist_id.id == current_pl]

        partner = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid).partner_id
        if not pcs or partner.property_product_pricelist.id != website_pl:
            pcs.append(partner.property_product_pricelist)
        # remove duplicates and sort by name
        pcs = sorted(set(pcs), key=lambda pl: pl.name)
        return [pl.id for pl in pcs]

    def get_pricelist_available(self, cr, uid, show_visible=False, context=None):
        """ Return the list of pricelists that can be used on website for the current user.
        Country restrictions will be detected with GeoIP (if installed).

        :param str country_code: code iso or False, If set, we search only price list available for this country
        :param bool show_visible: if True, we don't display pricelist where selectable is False (Eg: Code promo)

        :returns: pricelist recordset
        """
        isocountry = request.session.geoip and request.session.geoip.get('country_code') or False
        user_id = self.pool['res.users'].browse(cr, uid, request.uid or uid, context=context)
        pl_ids = self._get_pl(cr, uid, isocountry, show_visible,
                              user_id.partner_id.property_product_pricelist.id,
                              request.session.get('website_sale_current_pl'),
                              request.website.website_pricelist_ids)
        return self.pool['product.pricelist'].browse(cr, uid, pl_ids, context=context)

    def is_pricelist_available(self, cr, uid, pl_id, context=None):
        """ Return a boolean to specify if a specific pricelist can be manually set on the website.
        Warning: It check only if pricelist is in the 'selectable' pricelists or the current pricelist.

        :param int pl_id: The pricelist id to check

        :returns: Boolean, True if valid / available
        """
        return pl_id in [ppl.id for ppl in self.get_pricelist_available(cr, uid, show_visible=False, context=context)]

    def get_current_pricelist(self, cr, uid, context=None):
        """
        :returns: The current pricelist record
        """
        pl_id = request.session.get('website_sale_current_pl')
        if pl_id:
            return self.pool['product.pricelist'].browse(cr, uid, [pl_id], context=context)[0]
        else:
            pl = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.property_product_pricelist
            request.session['website_sale_current_pl'] = pl.id
            return pl

    def sale_product_domain(self, cr, uid, ids, context=None):
        return [("sale_ok", "=", True)]

    def get_partner(self, cr, uid):
        return self.pool['res.users'].browse(cr, SUPERUSER_ID, uid).partner_id

    def sale_get_order(self, cr, uid, ids, force_create=False, code=None, update_pricelist=False, force_pricelist=False, context=None):
        """ Return the current sale order after mofications specified by params.

        :param bool force_create: Create sale order if not already existing
        :param str code: Code to force a pricelist (promo code)
                         If empty, it's a special case to reset the pricelist with the first available else the default.
        :param bool update_pricelist: Force to recompute all the lines from sale order to adapt the price with the current pricelist.
        :param int force_pricelist: pricelist_id - if set,  we change the pricelist with this one

        :returns: browse record for the current sale order
        """
        partner = self.get_partner(cr, uid)
        sale_order_obj = self.pool['sale.order']
        sale_order_id = request.session.get('sale_order_id') or (partner.last_website_so_id.id if partner.last_website_so_id and partner.last_website_so_id.state == 'draft' else False)

        sale_order = None
        # Test validity of the sale_order_id
        if sale_order_id and sale_order_obj.exists(cr, SUPERUSER_ID, sale_order_id, context=context):
            sale_order = sale_order_obj.browse(cr, SUPERUSER_ID, sale_order_id, context=context)
        else:
            sale_order_id = None
        pricelist_id = request.session.get('website_sale_current_pl')

        if force_pricelist and self.pool['product.pricelist'].search_count(cr, uid, [('id', '=', force_pricelist)], context=context):
            pricelist_id = force_pricelist
            request.session['website_sale_current_pl'] = pricelist_id
            update_pricelist = True

        # create so if needed
        if not sale_order_id and (force_create or code):
            # TODO cache partner_id session
            user_obj = self.pool['res.users']
            affiliate_id = request.session.get('affiliate_id')
            salesperson_id = affiliate_id if user_obj.exists(cr, SUPERUSER_ID, affiliate_id, context=context) else request.website.salesperson_id.id
            for w in self.browse(cr, uid, ids):
                addr = partner.address_get(['delivery', 'invoice'])
                values = {
                    'partner_id': partner.id,
                    'pricelist_id': pricelist_id,
                    'payment_term_id': partner.property_payment_term_id.id if partner.property_payment_term_id else False,
                    'team_id': w.salesteam_id.id,
                    'partner_invoice_id': addr['invoice'],
                    'partner_shipping_id': addr['delivery'],
                    'user_id': salesperson_id or w.salesperson_id.id,
                }
                sale_order_id = sale_order_obj.create(cr, SUPERUSER_ID, values, context=context)
                request.session['sale_order_id'] = sale_order_id
                sale_order = sale_order_obj.browse(cr, SUPERUSER_ID, sale_order_id, context=context)

                if request.website.partner_id.id != partner.id:
                    self.pool['res.partner'].write(cr, SUPERUSER_ID, partner.id, {'last_website_so_id': sale_order_id})

        if sale_order_id:

            # check for change of pricelist with a coupon
            pricelist_id = pricelist_id or partner.property_product_pricelist.id

            # check for change of partner_id ie after signup
            if sale_order.partner_id.id != partner.id and request.website.partner_id.id != partner.id:
                flag_pricelist = False
                if pricelist_id != sale_order.pricelist_id.id:
                    flag_pricelist = True
                fiscal_position = sale_order.fiscal_position_id and sale_order.fiscal_position_id.id or False

                # change the partner, and trigger the onchange
                sale_order_obj.write(cr, SUPERUSER_ID, [sale_order_id], {'partner_id': partner.id}, context=context)
                sale_order_obj.onchange_partner_id(cr, SUPERUSER_ID, [sale_order_id], context=context)

                # check the pricelist : update it if the pricelist is not the 'forced' one
                values = {}
                if sale_order.pricelist_id:
                    if sale_order.pricelist_id.id != pricelist_id:
                        values['pricelist_id'] = pricelist_id
                        update_pricelist = True

                # if fiscal position, update the order lines taxes
                if sale_order.fiscal_position_id:
                    sale_order._compute_tax_id()

                # if values, then make the SO update
                if values:
                    sale_order_obj.write(cr, SUPERUSER_ID, [sale_order_id], values, context=context)

                # check if the fiscal position has changed with the partner_id update
                recent_fiscal_position = sale_order.fiscal_position_id and sale_order.fiscal_position_id.id or False
                if flag_pricelist or recent_fiscal_position != fiscal_position:
                    update_pricelist = True

            if code and code != sale_order.pricelist_id.code:
                pricelist_ids = self.pool['product.pricelist'].search(cr, uid, [('code', '=', code)], limit=1, context=context)
                if pricelist_ids:
                    pricelist_id = pricelist_ids[0]
                    update_pricelist = True
            elif code is not None and sale_order.pricelist_id.code:
                # code is not None when user removes code and click on "Apply"
                pricelist_id = partner.property_product_pricelist.id
                update_pricelist = True

            # update the pricelist
            if update_pricelist:
                request.session['website_sale_current_pl'] = pricelist_id
                values = {'pricelist_id': pricelist_id}
                sale_order.write(values)
                for line in sale_order.order_line:
                    if line.exists():
                        sale_order._cart_update(product_id=line.product_id.id, line_id=line.id, add_qty=0)

            # update browse record
            if (code and code != sale_order.pricelist_id.code) or sale_order.partner_id.id != partner.id or force_pricelist:
                sale_order = sale_order_obj.browse(cr, SUPERUSER_ID, sale_order.id, context=context)

        else:
            request.session['sale_order_id'] = None
            return None

        return sale_order

    def sale_get_transaction(self, cr, uid, ids, context=None):
        transaction_obj = self.pool.get('payment.transaction')
        tx_id = request.session.get('sale_transaction_id')
        if tx_id:
            tx_ids = transaction_obj.search(cr, SUPERUSER_ID, [('id', '=', tx_id), ('state', 'not in', ['cancel'])], context=context)
            if tx_ids:
                return transaction_obj.browse(cr, SUPERUSER_ID, tx_ids[0], context=context)
            else:
                request.session['sale_transaction_id'] = False
        return False

    def sale_reset(self, cr, uid, ids, context=None):
        request.session.update({
            'sale_order_id': False,
            'sale_transaction_id': False,
            'website_sale_current_pl': False,
        })
Example #27
0
class make_procurement(osv.osv_memory):
    _name = 'make.procurement'
    _description = 'Make Procurements'

    def onchange_product_id(self, cr, uid, ids, prod_id, context=None):
        product = self.pool.get('product.product').browse(cr,
                                                          uid,
                                                          prod_id,
                                                          context=context)
        return {
            'value': {
                'uom_id':
                product.uom_id.id,
                'product_tmpl_id':
                product.product_tmpl_id.id,
                'product_variant_count':
                product.product_tmpl_id.product_variant_count
            }
        }

    _columns = {
        'qty':
        fields.float('Quantity', digits=(16, 2), required=True),
        'res_model':
        fields.char('Res Model'),
        'product_id':
        fields.many2one('product.product', 'Product', required=True),
        'product_tmpl_id':
        fields.many2one('product.template', 'Template', required=True),
        'product_variant_count':
        fields.related('product_tmpl_id',
                       'product_variant_count',
                       type='integer',
                       string='Variant Number'),
        'uom_id':
        fields.many2one('product.uom', 'Unit of Measure', required=True),
        'warehouse_id':
        fields.many2one('stock.warehouse', 'Warehouse', required=True),
        'date_planned':
        fields.date('Planned Date', required=True),
        'route_ids':
        fields.many2many('stock.location.route', string='Preferred Routes'),
    }

    _defaults = {
        'date_planned': fields.date.context_today,
        'qty': lambda *args: 1.0,
    }

    def make_procurement(self, cr, uid, ids, context=None):
        """ Creates procurement order for selected product. """
        user = self.pool.get('res.users').browse(cr, uid, uid,
                                                 context=context).login
        wh_obj = self.pool.get('stock.warehouse')
        procurement_obj = self.pool.get('procurement.order')
        data_obj = self.pool.get('ir.model.data')

        for proc in self.browse(cr, uid, ids, context=context):
            wh = wh_obj.browse(cr, uid, proc.warehouse_id.id, context=context)
            procure_id = procurement_obj.create(
                cr, uid, {
                    'name': 'INT: ' + str(user),
                    'date_planned': proc.date_planned,
                    'product_id': proc.product_id.id,
                    'product_qty': proc.qty,
                    'product_uom': proc.uom_id.id,
                    'warehouse_id': proc.warehouse_id.id,
                    'location_id': wh.lot_stock_id.id,
                    'company_id': wh.company_id.id,
                    'route_ids': [(6, 0, proc.route_ids.ids)],
                })
            procurement_obj.signal_workflow(cr, uid, [procure_id],
                                            'button_confirm')

        id2 = data_obj._get_id(cr, uid, 'procurement', 'procurement_tree_view')
        id3 = data_obj._get_id(cr, uid, 'procurement', 'procurement_form_view')

        if id2:
            id2 = data_obj.browse(cr, uid, id2, context=context).res_id
        if id3:
            id3 = data_obj.browse(cr, uid, id3, context=context).res_id

        return {
            'view_type': 'form',
            'view_mode': 'tree,form',
            'res_model': 'procurement.order',
            'res_id': procure_id,
            'views': [(id3, 'form'), (id2, 'tree')],
            'type': 'ir.actions.act_window',
        }

    def default_get(self, cr, uid, fields, context=None):
        if context is None:
            context = {}
        record_id = context.get('active_id')

        if context.get('active_model') == 'product.template':
            product_ids = self.pool.get('product.product').search(
                cr,
                uid, [('product_tmpl_id', '=', context.get('active_id'))],
                context=context)
            if product_ids:
                record_id = product_ids[0]

        res = super(make_procurement, self).default_get(cr,
                                                        uid,
                                                        fields,
                                                        context=context)

        if record_id and 'product_id' in fields:
            proxy = self.pool.get('product.product')
            product_ids = proxy.search(cr,
                                       uid, [('id', '=', record_id)],
                                       context=context,
                                       limit=1)
            if product_ids:
                product_id = product_ids[0]

                product = self.pool.get('product.product').browse(
                    cr, uid, product_id, context=context)
                res['product_id'] = product.id
                res['uom_id'] = product.uom_id.id

        if 'warehouse_id' in fields:
            warehouse_id = self.pool.get('stock.warehouse').search(
                cr, uid, [], context=context)
            res['warehouse_id'] = warehouse_id[0] if warehouse_id else False

        return res

    def create(self, cr, uid, values, context=None):
        if values.get('product_id'):
            values.update(
                self.onchange_product_id(cr,
                                         uid,
                                         None,
                                         values['product_id'],
                                         context=context)['value'])
        return super(make_procurement, self).create(cr,
                                                    uid,
                                                    values,
                                                    context=context)
Example #28
0
class hr_timesheet_sheet(osv.osv):
    _name = "hr_timesheet_sheet.sheet"
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _table = 'hr_timesheet_sheet_sheet'
    _order = "id desc"
    _description = "Timesheet"

    def _total(self, cr, uid, ids, name, args, context=None):
        """ Compute the attendances, analytic lines timesheets and differences between them
            for all the days of a timesheet and the current day
        """
        res = dict.fromkeys(
            ids, {
                'total_attendance': 0.0,
                'total_timesheet': 0.0,
                'total_difference': 0.0,
            })

        cr.execute(
            """
            SELECT sheet_id as id,
                   sum(total_attendance) as total_attendance,
                   sum(total_timesheet) as total_timesheet,
                   sum(total_difference) as  total_difference
            FROM hr_timesheet_sheet_sheet_day
            WHERE sheet_id IN %s
            GROUP BY sheet_id
        """, (tuple(ids), ))

        res.update(dict((x.pop('id'), x) for x in cr.dictfetchall()))

        return res

    def check_employee_attendance_state(self, cr, uid, sheet_id, context=None):
        ids_signin = self.pool.get('hr.attendance').search(
            cr, uid, [('sheet_id', '=', sheet_id), ('action', '=', 'sign_in')])
        ids_signout = self.pool.get('hr.attendance').search(
            cr, uid, [('sheet_id', '=', sheet_id),
                      ('action', '=', 'sign_out')])

        if len(ids_signin) != len(ids_signout):
            raise UserError(
                _('The timesheet cannot be validated as it does not contain an equal number of sign ins and sign outs.'
                  ))
        return True

    def copy(self, cr, uid, ids, *args, **argv):
        raise UserError(_('You cannot duplicate a timesheet.'))

    def create(self, cr, uid, vals, context=None):
        if 'employee_id' in vals:
            if not self.pool.get('hr.employee').browse(
                    cr, uid, vals['employee_id'], context=context).user_id:
                raise UserError(
                    _('In order to create a timesheet for this employee, you must link him/her to a user.'
                      ))
        if vals.get('attendances_ids'):
            # If attendances, we sort them by date asc before writing them, to satisfy the alternance constraint
            vals['attendances_ids'] = self.sort_attendances(
                cr, uid, vals['attendances_ids'], context=context)
        return super(hr_timesheet_sheet, self).create(cr,
                                                      uid,
                                                      vals,
                                                      context=context)

    def write(self, cr, uid, ids, vals, context=None):
        if 'employee_id' in vals:
            new_user_id = self.pool.get('hr.employee').browse(
                cr, uid, vals['employee_id'],
                context=context).user_id.id or False
            if not new_user_id:
                raise UserError(
                    _('In order to create a timesheet for this employee, you must link him/her to a user.'
                      ))
            if not self._sheet_date(
                    cr, uid, ids, forced_user_id=new_user_id, context=context):
                raise UserError(
                    _('You cannot have 2 timesheets that overlap!\nYou should use the menu \'My Timesheet\' to avoid this problem.'
                      ))
            if not self.pool.get('hr.employee').browse(
                    cr, uid, vals['employee_id'], context=context).product_id:
                raise UserError(
                    _('In order to create a timesheet for this employee, you must link the employee to a product.'
                      ))
        if vals.get('attendances_ids'):
            # If attendances, we sort them by date asc before writing them, to satisfy the alternance constraint
            # In addition to the date order, deleting attendances are done before inserting attendances
            vals['attendances_ids'] = self.sort_attendances(
                cr, uid, vals['attendances_ids'], context=context)
        res = super(hr_timesheet_sheet, self).write(cr,
                                                    uid,
                                                    ids,
                                                    vals,
                                                    context=context)
        if vals.get('attendances_ids'):
            for timesheet in self.browse(cr, uid, ids):
                if not self.pool['hr.attendance']._altern_si_so(
                        cr, uid, [att.id
                                  for att in timesheet.attendances_ids]):
                    raise UserError(
                        _('Error ! Sign in (resp. Sign out) must follow Sign out (resp. Sign in)'
                          ))
        return res

    def sort_attendances(self, cr, uid, attendance_tuples, context=None):
        date_attendances = []
        for att_tuple in attendance_tuples:
            if att_tuple[0] in [0, 1, 4]:
                if att_tuple[0] in [0, 1]:
                    if att_tuple[2] and att_tuple[2].has_key('name'):
                        name = att_tuple[2]['name']
                    else:
                        name = self.pool['hr.attendance'].browse(
                            cr, uid, att_tuple[1]).name
                else:
                    name = self.pool['hr.attendance'].browse(
                        cr, uid, att_tuple[1]).name
                date_attendances.append((1, name, att_tuple))
            elif att_tuple[0] in [2, 3]:
                date_attendances.append((0, self.pool['hr.attendance'].browse(
                    cr, uid, att_tuple[1]).name, att_tuple))
            else:
                date_attendances.append((0, False, att_tuple))
        date_attendances.sort()
        return [att[2] for att in date_attendances]

    def button_confirm(self, cr, uid, ids, context=None):
        for sheet in self.browse(cr, uid, ids, context=context):
            if sheet.employee_id and sheet.employee_id.parent_id and sheet.employee_id.parent_id.user_id:
                self.message_subscribe_users(
                    cr,
                    uid, [sheet.id],
                    user_ids=[sheet.employee_id.parent_id.user_id.id],
                    context=context)
            self.check_employee_attendance_state(cr,
                                                 uid,
                                                 sheet.id,
                                                 context=context)
            di = sheet.user_id.company_id.timesheet_max_difference
            if (abs(sheet.total_difference) < di) or not di:
                sheet.signal_workflow('confirm')
            else:
                raise UserError(
                    _('Please verify that the total difference of the sheet is lower than %.2f.'
                      ) % (di, ))
        return True

    def attendance_action_change(self, cr, uid, ids, context=None):
        hr_employee = self.pool.get('hr.employee')
        employee_ids = []
        for sheet in self.browse(cr, uid, ids, context=context):
            if sheet.employee_id.id not in employee_ids:
                employee_ids.append(sheet.employee_id.id)
        return hr_employee.attendance_action_change(cr,
                                                    uid,
                                                    employee_ids,
                                                    context=context)

    def _count_attendances(self, cr, uid, ids, field_name, arg, context=None):
        res = dict.fromkeys(ids, 0)
        attendances_groups = self.pool['hr.attendance'].read_group(
            cr,
            uid, [('sheet_id', 'in', ids)], ['sheet_id'],
            'sheet_id',
            context=context)
        for attendances in attendances_groups:
            res[attendances['sheet_id'][0]] = attendances['sheet_id_count']
        return res

    _columns = {
        'name':
        fields.char('Note',
                    select=1,
                    states={
                        'confirm': [('readonly', True)],
                        'done': [('readonly', True)]
                    }),
        'employee_id':
        fields.many2one('hr.employee', 'Employee', required=True),
        'user_id':
        fields.related(
            'employee_id',
            'user_id',
            type="many2one",
            relation="res.users",
            store=True,
            string="User",
            required=False,
            readonly=True
        ),  #fields.many2one('res.users', 'User', required=True, select=1, states={'confirm':[('readonly', True)], 'done':[('readonly', True)]}),
        'date_from':
        fields.date('Date from',
                    required=True,
                    select=1,
                    readonly=True,
                    states={'new': [('readonly', False)]}),
        'date_to':
        fields.date('Date to',
                    required=True,
                    select=1,
                    readonly=True,
                    states={'new': [('readonly', False)]}),
        'timesheet_ids':
        fields.one2many('account.analytic.line',
                        'sheet_id',
                        'Timesheet lines',
                        readonly=True,
                        states={
                            'draft': [('readonly', False)],
                            'new': [('readonly', False)]
                        }),
        'attendances_ids':
        fields.one2many('hr.attendance', 'sheet_id', 'Attendances'),
        'state':
        fields.selection(
            [('new', 'New'), ('draft', 'Open'),
             ('confirm', 'Waiting Approval'), ('done', 'Approved')],
            'Status',
            select=True,
            required=True,
            readonly=True,
            track_visibility='onchange',
            help=
            ' * The \'Draft\' status is used when a user is encoding a new and unconfirmed timesheet. \
                \n* The \'Confirmed\' status is used for to confirm the timesheet by user. \
                \n* The \'Done\' status is used when users timesheet is accepted by his/her senior.'
        ),
        'state_attendance':
        fields.related('employee_id',
                       'state',
                       type='selection',
                       selection=[('absent', 'Absent'),
                                  ('present', 'Present')],
                       string='Current Status',
                       readonly=True),
        'total_attendance':
        fields.function(_total,
                        method=True,
                        string='Total Attendance',
                        multi="_total"),
        'total_timesheet':
        fields.function(_total,
                        method=True,
                        string='Total Timesheet',
                        multi="_total"),
        'total_difference':
        fields.function(_total,
                        method=True,
                        string='Difference',
                        multi="_total"),
        'period_ids':
        fields.one2many('hr_timesheet_sheet.sheet.day',
                        'sheet_id',
                        'Period',
                        readonly=True),
        'account_ids':
        fields.one2many('hr_timesheet_sheet.sheet.account',
                        'sheet_id',
                        'Analytic accounts',
                        readonly=True),
        'company_id':
        fields.many2one('res.company', 'Company'),
        'department_id':
        fields.many2one('hr.department', 'Department'),
        'attendance_count':
        fields.function(_count_attendances,
                        type='integer',
                        string="Attendances"),
    }

    def _default_date_from(self, cr, uid, context=None):
        user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
        r = user.company_id and user.company_id.timesheet_range or 'month'
        if r == 'month':
            return time.strftime('%Y-%m-01')
        elif r == 'week':
            return (datetime.today() +
                    relativedelta(weekday=0, days=-6)).strftime('%Y-%m-%d')
        elif r == 'year':
            return time.strftime('%Y-01-01')
        return fields.date.context_today(self, cr, uid, context)

    def _default_date_to(self, cr, uid, context=None):
        user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
        r = user.company_id and user.company_id.timesheet_range or 'month'
        if r == 'month':
            return (
                datetime.today() +
                relativedelta(months=+1, day=1, days=-1)).strftime('%Y-%m-%d')
        elif r == 'week':
            return (datetime.today() +
                    relativedelta(weekday=6)).strftime('%Y-%m-%d')
        elif r == 'year':
            return time.strftime('%Y-12-31')
        return fields.date.context_today(self, cr, uid, context)

    def _default_employee(self, cr, uid, context=None):
        emp_ids = self.pool.get('hr.employee').search(cr,
                                                      uid,
                                                      [('user_id', '=', uid)],
                                                      context=context)
        return emp_ids and emp_ids[0] or False

    _defaults = {
        'date_from':
        _default_date_from,
        'date_to':
        _default_date_to,
        'state':
        'new',
        'employee_id':
        _default_employee,
        'company_id':
        lambda self, cr, uid, c: self.pool.get('res.company').
        _company_default_get(cr, uid, 'hr_timesheet_sheet.sheet', context=c)
    }

    def _sheet_date(self, cr, uid, ids, forced_user_id=False, context=None):
        for sheet in self.browse(cr, uid, ids, context=context):
            new_user_id = forced_user_id or sheet.employee_id.user_id and sheet.employee_id.user_id.id
            if new_user_id:
                cr.execute(
                    'SELECT id \
                    FROM hr_timesheet_sheet_sheet \
                    WHERE (date_from <= %s and %s <= date_to) \
                        AND user_id=%s \
                        AND id <> %s',
                    (sheet.date_to, sheet.date_from, new_user_id, sheet.id))
                if cr.fetchall():
                    return False
        return True

    _constraints = [
        (_sheet_date,
         'You cannot have 2 timesheets that overlap!\nPlease use the menu \'My Current Timesheet\' to avoid this problem.',
         ['date_from', 'date_to']),
    ]

    def action_set_to_draft(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'draft'})
        self.create_workflow(cr, uid, ids)
        return True

    def name_get(self, cr, uid, ids, context=None):
        if not ids:
            return []
        if isinstance(ids, (long, int)):
            ids = [ids]
        # week number according to ISO 8601 Calendar
        return [(r['id'], _('Week ')+str(datetime.strptime(r['date_from'], '%Y-%m-%d').isocalendar()[1])) \
                for r in self.read(cr, uid, ids, ['date_from'],
                    context=context, load='_classic_write')]

    def unlink(self, cr, uid, ids, context=None):
        sheets = self.read(cr,
                           uid,
                           ids, ['state', 'total_attendance'],
                           context=context)
        for sheet in sheets:
            if sheet['state'] in ('confirm', 'done'):
                raise UserError(
                    _('You cannot delete a timesheet which is already confirmed.'
                      ))
            elif sheet['total_attendance'] <> 0.00:
                raise UserError(
                    _('You cannot delete a timesheet which have attendance entries.'
                      ))

        toremove = []
        analytic_timesheet = self.pool.get('account.analytic.line')
        for sheet in self.browse(cr, uid, ids, context=context):
            for timesheet in sheet.timesheet_ids:
                toremove.append(timesheet.id)
        analytic_timesheet.unlink(cr, uid, toremove, context=context)

        return super(hr_timesheet_sheet, self).unlink(cr,
                                                      uid,
                                                      ids,
                                                      context=context)

    def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
        department_id = False
        user_id = False
        if employee_id:
            empl_id = self.pool.get('hr.employee').browse(cr,
                                                          uid,
                                                          employee_id,
                                                          context=context)
            department_id = empl_id.department_id.id
            user_id = empl_id.user_id.id
        return {
            'value': {
                'department_id': department_id,
                'user_id': user_id,
            }
        }

    # ------------------------------------------------
    # OpenChatter methods and notifications
    # ------------------------------------------------

    def _track_subtype(self, cr, uid, ids, init_values, context=None):
        record = self.browse(cr, uid, ids[0], context=context)
        if 'state' in init_values and record.state == 'confirm':
            return 'hr_timesheet_sheet.mt_timesheet_confirmed'
        elif 'state' in init_values and record.state == 'done':
            return 'hr_timesheet_sheet.mt_timesheet_approved'
        return super(hr_timesheet_sheet, self)._track_subtype(cr,
                                                              uid,
                                                              ids,
                                                              init_values,
                                                              context=context)

    def _needaction_domain_get(self, cr, uid, context=None):
        emp_obj = self.pool.get('hr.employee')
        empids = emp_obj.search(cr,
                                uid, [('parent_id.user_id', '=', uid)],
                                context=context)
        if not empids:
            return False
        dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
        return dom
class account_bank_statement(osv.osv):
    _inherit = 'account.bank.statement'
    _columns = {
        'pos_session_id' : fields.many2one('pos.session', string="Session", copy=False),
        'account_id': fields.related('journal_id', 'default_debit_account_id', type='many2one', relation='account.account', readonly=True),
    }
Example #30
0
class marketing_campaign_segment(osv.osv):
    _name = "marketing.campaign.segment"
    _description = "Campaign Segment"
    _order = "name"

    def _get_next_sync(self, cr, uid, ids, fn, args, context=None):
        # next auto sync date is same for all segments
        sync_job = self.pool.get('ir.model.data').get_object(cr, SUPERUSER_ID, 'marketing_campaign', 'ir_cron_marketing_campaign_every_day', context=context)
        next_sync = sync_job and sync_job.nextcall or False
        return dict.fromkeys(ids, next_sync)

    _columns = {
        'name': fields.char('Name', required=True),
        'campaign_id': fields.many2one('marketing.campaign', 'Campaign', required=True, select=1, ondelete="cascade"),
        'object_id': fields.related('campaign_id','object_id', type='many2one', relation='ir.model', string='Resource'),
        'ir_filter_id': fields.many2one('ir.filters', 'Filter', ondelete="restrict",
                            domain=lambda self: [('model_id', '=', self.object_id._name)],
                            help="Filter to select the matching resource records that belong to this segment. "\
                                 "New filters can be created and saved using the advanced search on the list view of the Resource. "\
                                 "If no filter is set, all records are selected without filtering. "\
                                 "The synchronization mode may also add a criterion to the filter."),
        'sync_last_date': fields.datetime('Last Synchronization', help="Date on which this segment was synchronized last time (automatically or manually)"),
        'sync_mode': fields.selection([('create_date', 'Only records created after last sync'),
                                      ('write_date', 'Only records modified after last sync (no duplicates)'),
                                      ('all', 'All records (no duplicates)')],
                                      'Synchronization mode',
                                      help="Determines an additional criterion to add to the filter when selecting new records to inject in the campaign. "\
                                           '"No duplicates" prevents selecting records which have already entered the campaign previously.'\
                                           'If the campaign has a "unique field" set, "no duplicates" will also prevent selecting records which have '\
                                           'the same value for the unique field as other records that already entered the campaign.'),
        'state': fields.selection([('draft', 'New'),
                                   ('cancelled', 'Cancelled'),
                                   ('running', 'Running'),
                                   ('done', 'Done')],
                                   'Status', copy=False),
        'date_run': fields.datetime('Launch Date', help="Initial start date of this segment."),
        'date_done': fields.datetime('End Date', help="Date this segment was last closed or cancelled."),
        'date_next_sync': fields.function(_get_next_sync, string='Next Synchronization', type='datetime', help="Next time the synchronization job is scheduled to run automatically"),
    }

    _defaults = {
        'state': lambda *a: 'draft',
        'sync_mode': lambda *a: 'create_date',
    }

    def _check_model(self, cr, uid, ids, context=None):
        for obj in self.browse(cr, uid, ids, context=context):
            if not obj.ir_filter_id:
                return True
            if obj.campaign_id.object_id.model != obj.ir_filter_id.model_id:
                return False
        return True

    _constraints = [
        (_check_model, 'Model of filter must be same as resource model of Campaign ', ['ir_filter_id,campaign_id']),
    ]

    def onchange_campaign_id(self, cr, uid, ids, campaign_id):
        res = {'domain':{'ir_filter_id':[]}}
        campaign_pool = self.pool.get('marketing.campaign')
        if campaign_id:
            campaign = campaign_pool.browse(cr, uid, campaign_id)
            model_name = self.pool.get('ir.model').read(cr, uid, [campaign.object_id.id], ['model'])
            if model_name:
                mod_name = model_name[0]['model']
                res['domain'] = {'ir_filter_id': [('model_id', '=', mod_name)]}
        else:
            res['value'] = {'ir_filter_id': False}
        return res

    def state_running_set(self, cr, uid, ids, *args):
        segment = self.browse(cr, uid, ids[0])
        vals = {'state': 'running'}
        if not segment.date_run:
            vals['date_run'] = time.strftime('%Y-%m-%d %H:%M:%S')
        self.write(cr, uid, ids, vals)
        return True

    def state_done_set(self, cr, uid, ids, *args):
        wi_ids = self.pool.get("marketing.campaign.workitem").search(cr, uid,
                                [('state', '=', 'todo'), ('segment_id', 'in', ids)])
        self.pool.get("marketing.campaign.workitem").write(cr, uid, wi_ids, {'state':'cancelled'})
        self.write(cr, uid, ids, {'state': 'done','date_done': time.strftime('%Y-%m-%d %H:%M:%S')})
        return True

    def state_cancel_set(self, cr, uid, ids, *args):
        wi_ids = self.pool.get("marketing.campaign.workitem").search(cr, uid,
                                [('state', '=', 'todo'), ('segment_id', 'in', ids)])
        self.pool.get("marketing.campaign.workitem").write(cr, uid, wi_ids, {'state':'cancelled'})
        self.write(cr, uid, ids, {'state': 'cancelled','date_done': time.strftime('%Y-%m-%d %H:%M:%S')})
        return True

    def synchroniz(self, cr, uid, ids, *args):
        self.process_segment(cr, uid, ids)
        return True

    @api.cr_uid_ids_context
    def process_segment(self, cr, uid, segment_ids=None, context=None):
        Workitems = self.pool.get('marketing.campaign.workitem')
        Campaigns = self.pool.get('marketing.campaign')
        if not segment_ids:
            segment_ids = self.search(cr, uid, [('state', '=', 'running')], context=context)

        action_date = time.strftime('%Y-%m-%d %H:%M:%S')
        campaigns = set()
        for segment in self.browse(cr, uid, segment_ids, context=context):
            if segment.campaign_id.state != 'running':
                continue

            campaigns.add(segment.campaign_id.id)
            act_ids = self.pool.get('marketing.campaign.activity').search(cr,
                  uid, [('start', '=', True), ('campaign_id', '=', segment.campaign_id.id)], context=context)

            model_obj = self.pool[segment.object_id.model]
            criteria = []
            if segment.sync_last_date and segment.sync_mode != 'all':
                criteria += [(segment.sync_mode, '>', segment.sync_last_date)]
            if segment.ir_filter_id:
                criteria += eval(segment.ir_filter_id.domain)
            object_ids = model_obj.search(cr, uid, criteria, context=context)

            # XXX TODO: rewrite this loop more efficiently without doing 1 search per record!
            for record in model_obj.browse(cr, uid, object_ids, context=context):
                # avoid duplicate workitem for the same resource
                if segment.sync_mode in ('write_date','all'):
                    if Campaigns._find_duplicate_workitems(cr, uid, record, segment.campaign_id, context=context):
                        continue

                wi_vals = {
                    'segment_id': segment.id,
                    'date': action_date,
                    'state': 'todo',
                    'res_id': record.id
                }

                partner = self.pool.get('marketing.campaign')._get_partner_for(segment.campaign_id, record)
                if partner:
                    wi_vals['partner_id'] = partner.id

                for act_id in act_ids:
                    wi_vals['activity_id'] = act_id
                    Workitems.create(cr, uid, wi_vals, context=context)

            self.write(cr, uid, segment.id, {'sync_last_date':action_date}, context=context)
        Workitems.process_all(cr, uid, list(campaigns), context=context)
        return True