Beispiel #1
0
class HeaderHTML(osv.osv):
    """HTML Header allows you to define HTML CSS and Page format"""

    _name = "ir.header_webkit"
    _columns = {
        'company_id' : fields.many2one('res.company', 'Company'),
        'html' : fields.text('webkit header', help="Set Webkit Report Header"),
        'footer_html' : fields.text('webkit footer', help="Set Webkit Report Footer."),
        'css' : fields.text('Header CSS'),
        'name' : fields.char('Name', required=True),
        'margin_top' : fields.float('Top Margin (mm)'),
        'margin_bottom' : fields.float('Bottom Margin (mm)'),
        'margin_left' : fields.float('Left Margin (mm)'),
        'margin_right' : fields.float('Right Margin (mm)'),
        'orientation' : fields.selection(
                        [('Landscape','Landscape'),('Portrait', 'Portrait')],
                        'Orientation',
                        ),
        'format': fields.selection(
                [
                ('A0' ,'A0  5   841 x 1189 mm'),
                ('A1' ,'A1  6   594 x 841 mm'),
                ('A2' ,'A2  7   420 x 594 mm'),
                ('A3' ,'A3  8   297 x 420 mm'),
                ('A4' ,'A4  0   210 x 297 mm, 8.26 x 11.69 inches'),
                ('A5' ,'A5  9   148 x 210 mm'),
                ('A6' ,'A6  10  105 x 148 mm'),
                ('A7' ,'A7  11  74 x 105 mm'),
                ('A8' ,'A8  12  52 x 74 mm'),
                ('A9' ,'A9  13  37 x 52 mm'),
                ('B0' ,'B0  14  1000 x 1414 mm'),
                ('B1' ,'B1  15  707 x 1000 mm'),
                ('B2' ,'B2  17  500 x 707 mm'),
                ('B3' ,'B3  18  353 x 500 mm'),
                ('B4' ,'B4  19  250 x 353 mm'),
                ('B5' ,'B5  1   176 x 250 mm, 6.93 x 9.84 inches'),
                ('B6' ,'B6  20  125 x 176 mm'),
                ('B7' ,'B7  21  88 x 125 mm'),
                ('B8' ,'B8  22  62 x 88 mm'),
                ('B9' ,'B9  23  33 x 62 mm'),
                ('B10',':B10    16  31 x 44 mm'),
                ('C5E','C5E 24  163 x 229 mm'),
                ('Comm10E','Comm10E 25  105 x 241 mm, U.S. Common 10 Envelope'),
                ('DLE', 'DLE 26 110 x 220 mm'),
                ('Executive','Executive 4   7.5 x 10 inches, 190.5 x 254 mm'),
                ('Folio','Folio 27  210 x 330 mm'),
                ('Ledger', 'Ledger  28  431.8 x 279.4 mm'),
                ('Legal', 'Legal    3   8.5 x 14 inches, 215.9 x 355.6 mm'),
                ('Letter','Letter 2 8.5 x 11 inches, 215.9 x 279.4 mm'),
                ('Tabloid', 'Tabloid 29 279.4 x 431.8 mm'),
                ],
                'Paper size',
                required=True,
                help="Select Proper Paper size"
        )
    }
Beispiel #2
0
class wizard_price(osv.osv):
    _name = "wizard.price"
    _description = "Compute price wizard"
    _columns = {
        'info_field':
        fields.text('Info', readonly=True),
        'real_time_accounting':
        fields.boolean("Generate accounting entries when real-time"),
        'recursive':
        fields.boolean("Change prices of child BoMs too"),
    }

    def default_get(self, cr, uid, fields, context=None):
        res = super(wizard_price, self).default_get(cr,
                                                    uid,
                                                    fields,
                                                    context=context)
        product_pool = self.pool.get('product.template')
        product_obj = product_pool.browse(cr, uid,
                                          context.get('active_id', False))
        if context is None:
            context = {}
        rec_id = context and context.get('active_id', False)
        assert rec_id, _('Active ID is not set in Context.')
        computed_price = product_pool.compute_price(
            cr,
            uid, [],
            template_ids=[product_obj.id],
            test=True,
            context=context)
        if product_obj.id in computed_price:
            res['info_field'] = "%s: %s" % (product_obj.name,
                                            computed_price[product_obj.id])
        else:
            res['info_field'] = ""
        return res

    def compute_from_bom(self, cr, uid, ids, context=None):
        assert len(ids) == 1
        if context is None:
            context = {}
        model = context.get('active_model')
        if model != 'product.template':
            raise UserError(
                _('This wizard is build for product templates, while you are currently running it from a product variant.'
                  ))
        rec_id = context and context.get('active_id', False)
        assert rec_id, _('Active ID is not set in Context.')
        prod_obj = self.pool.get('product.template')
        res = self.browse(cr, uid, ids, context=context)
        prod = prod_obj.browse(cr, uid, rec_id, context=context)
        prod_obj.compute_price(
            cr,
            uid, [],
            template_ids=[prod.id],
            real_time_accounting=res[0].real_time_accounting,
            recursive=res[0].recursive,
            test=False,
            context=context)
Beispiel #3
0
class account_fiscal_position_template(osv.osv):
    _inherit = 'account.fiscal.position.template'
    _columns = {
        'name':
        fields.char('Fiscal Position Template', required=True, translate=True),
        'note':
        fields.text('Notes', translate=True),
    }
Beispiel #4
0
class WebsiteResPartner(osv.Model):
    _name = 'res.partner'
    _inherit = [
        'res.partner', 'website.seo.metadata', 'website.published.mixin'
    ]

    def _get_ids(self, cr, uid, ids, flds, args, context=None):
        return {i: i for i in ids}

    def _set_private(self, cr, uid, ids, field_name, value, arg, context=None):
        return self.write(cr,
                          uid,
                          ids, {'website_published': not value},
                          context=context)

    def _get_private(self, cr, uid, ids, field_name, arg, context=None):
        return dict((rec.id, not rec.website_published)
                    for rec in self.browse(cr, uid, ids, context=context))

    def _search_private(self, cr, uid, obj, name, args, context=None):
        return [('website_published', '=', not args[0][2])]

    _columns = {
        'website_private':
        fields.function(_get_private,
                        fnct_inv=_set_private,
                        fnct_search=_search_private,
                        type='boolean',
                        string='Private Profile'),
        'website_description':
        fields.html('Website Partner Full Description', strip_style=True),
        'website_short_description':
        fields.text('Website Partner Short Description'),
        # hack to allow using plain browse record in qweb views
        'self':
        fields.function(_get_ids, type='many2one', relation=_name),
    }

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

    _defaults = {
        'website_private': True,
    }
Beispiel #5
0
class res_partner(osv.osv):
    _inherit = 'res.partner'
    _columns = {
        'sale_warn':
        fields.selection(WARNING_MESSAGE,
                         'Sales Order',
                         help=WARNING_HELP,
                         required=True),
        'sale_warn_msg':
        fields.text('Message for Sales Order'),
        'purchase_warn':
        fields.selection(WARNING_MESSAGE,
                         'Purchase Order',
                         help=WARNING_HELP,
                         required=True),
        'purchase_warn_msg':
        fields.text('Message for Purchase Order'),
        'picking_warn':
        fields.selection(WARNING_MESSAGE,
                         'Stock Picking',
                         help=WARNING_HELP,
                         required=True),
        'picking_warn_msg':
        fields.text('Message for Stock Picking'),
        'invoice_warn':
        fields.selection(WARNING_MESSAGE,
                         'Invoice',
                         help=WARNING_HELP,
                         required=True),
        'invoice_warn_msg':
        fields.text('Message for Invoice'),
    }
    _defaults = {
        'sale_warn': 'no-message',
        'purchase_warn': 'no-message',
        'picking_warn': 'no-message',
        'invoice_warn': 'no-message',
    }
Beispiel #6
0
class product_product(osv.osv):
    _inherit = 'product.template'
    _columns = {
        'sale_line_warn':
        fields.selection(WARNING_MESSAGE,
                         'Sales Order Line',
                         help=WARNING_HELP,
                         required=True),
        'sale_line_warn_msg':
        fields.text('Message for Sales Order Line'),
        'purchase_line_warn':
        fields.selection(WARNING_MESSAGE,
                         'Purchase Order Line',
                         help=WARNING_HELP,
                         required=True),
        'purchase_line_warn_msg':
        fields.text('Message for Purchase Order Line'),
    }

    _defaults = {
        'sale_line_warn': 'no-message',
        'purchase_line_warn': 'no-message',
    }
Beispiel #7
0
class WebsiteTwitterTweet(osv.osv):
    _name = "website.twitter.tweet"
    _description = "Twitter Tweets"
    _columns = {
        'website_id': fields.many2one('website', string="Website"),
        'screen_name': fields.char("Screen Name"),
        'tweet': fields.text('Tweets'),

        # Twitter IDs are 64-bit unsigned ints, so we need to store them in
        # unlimited precision NUMERIC columns, which can be done with a
        # float field. Used digits=(0,0) to indicate unlimited.
        # Using VARCHAR would work too but would have sorting problems.
        'tweet_id': fields.float("Tweet ID", digits=(0, 0)),  # Twitter
    }
Beispiel #8
0
class test_converter(orm.Model):
    _name = 'web_editor.converter.test'

    # disable translation export for those brilliant field labels and values
    _translate = False

    _columns = {
        'char':
        fields.char(),
        'integer':
        fields.integer(),
        'float':
        fields.float(),
        'numeric':
        fields.float(digits=(16, 2)),
        'many2one':
        fields.many2one('web_editor.converter.test.sub'),
        'binary':
        fields.binary(),
        'date':
        fields.date(),
        'datetime':
        fields.datetime(),
        'selection':
        fields.selection([
            (1, "réponse A"),
            (2, "réponse B"),
            (3, "réponse C"),
            (4, "réponse D"),
        ]),
        'selection_str':
        fields.selection(
            [
                ('A', "Qu'il n'est pas arrivé à Toronto"),
                ('B', "Qu'il était supposé arriver à Toronto"),
                ('C', "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"),
                ('D', "La réponse D"),
            ],
            string=
            u"Lorsqu'un pancake prend l'avion à destination de Toronto et "
            u"qu'il fait une escale technique à St Claude, on dit:"),
        'html':
        fields.html(),
        'text':
        fields.text(),
    }
Beispiel #9
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)
Beispiel #10
0
class sale_quote_template(osv.osv):
    _name = "sale.quote.template"
    _description = "Sale Quotation Template"
    _columns = {
        'name':
        fields.char('Quotation Template', required=True),
        'website_description':
        fields.html('Description', translate=True),
        'quote_line':
        fields.one2many('sale.quote.line',
                        'quote_id',
                        'Quotation Template Lines',
                        copy=True),
        'note':
        fields.text('Terms and conditions'),
        'options':
        fields.one2many('sale.quote.option',
                        'template_id',
                        'Optional Products Lines',
                        copy=True),
        'number_of_days':
        fields.integer(
            'Quotation Duration',
            help=
            'Number of days for the validity date computation of the quotation'
        ),
        'require_payment':
        fields.selection(
            [(0, 'Not mandatory on website quote validation'),
             (1, 'Immediate after website order validation')],
            'Payment',
            help=
            "Require immediate payment by the customer when validating the order from the website quote"
        ),
    }

    def open_template(self, cr, uid, quote_id, context=None):
        return {
            'type': 'ir.actions.act_url',
            'target': 'self',
            'url': '/quote/template/%d' % quote_id[0]
        }
Beispiel #11
0
class grant_badge_wizard(osv.TransientModel):
    """ Wizard allowing to grant a badge to a user"""

    _name = 'gamification.badge.user.wizard'
    _columns = {
        'user_id':
        fields.many2one("res.users", string='User', required=True),
        'badge_id':
        fields.many2one("gamification.badge", string='Badge', required=True),
        'comment':
        fields.text('Comment'),
    }

    def action_grant_badge(self, cr, uid, ids, context=None):
        """Wizard action for sending a badge to a chosen user"""

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

        for wiz in self.browse(cr, uid, ids, context=context):
            if uid == wiz.user_id.id:
                raise UserError(_('You can not grant a badge to yourself'))

            #create the badge
            values = {
                'user_id': wiz.user_id.id,
                'sender_id': uid,
                'badge_id': wiz.badge_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
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)
Beispiel #13
0
class gamification_badge(osv.Model):
    """Badge object that users can send and receive"""

    CAN_GRANT = 1
    NOBODY_CAN_GRANT = 2
    USER_NOT_VIP = 3
    BADGE_REQUIRED = 4
    TOO_MANY = 5

    _name = 'gamification.badge'
    _description = 'Gamification badge'
    _inherit = ['mail.thread']

    def _get_owners_info(self, cr, uid, ids, name, args, context=None):
        """Return:
            the list of unique res.users ids having received this badge
            the total number of time this badge was granted
            the total number of users this badge was granted to
        """
        result = dict((res_id, {'stat_count': 0, 'stat_count_distinct': 0, 'unique_owner_ids': []}) for res_id in ids)

        cr.execute("""
            SELECT badge_id, count(user_id) as stat_count,
                count(distinct(user_id)) as stat_count_distinct,
                array_agg(distinct(user_id)) as unique_owner_ids
            FROM gamification_badge_user
            WHERE badge_id in %s
            GROUP BY badge_id
            """, (tuple(ids),))
        for (badge_id, stat_count, stat_count_distinct, unique_owner_ids) in cr.fetchall():
            result[badge_id] = {
                'stat_count': stat_count,
                'stat_count_distinct': stat_count_distinct,
                'unique_owner_ids': unique_owner_ids,
            }
        return result

    def _get_badge_user_stats(self, cr, uid, ids, name, args, context=None):
        """Return stats related to badge users"""
        result = dict.fromkeys(ids, False)
        badge_user_obj = self.pool.get('gamification.badge.user')
        first_month_day = date.today().replace(day=1).strftime(DF)
        for bid in ids:
            result[bid] = {
                'stat_my': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid)], context=context, count=True),
                'stat_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_date', '>=', first_month_day)], context=context, count=True),
                'stat_my_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True),
                'stat_my_monthly_sending': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_uid', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True)
            }
        return result

    def _remaining_sending_calc(self, cr, uid, ids, name, args, context=None):
        """Computes the number of badges remaining the user can send

        0 if not allowed or no remaining
        integer if limited sending
        -1 if infinite (should not be displayed)
        """
        result = dict.fromkeys(ids, False)
        for badge in self.browse(cr, uid, ids, context=context):
            if self._can_grant_badge(cr, uid, badge.id, context) != 1:
                # if the user cannot grant this badge at all, result is 0
                result[badge.id] = 0
            elif not badge.rule_max:
                # if there is no limitation, -1 is returned which means 'infinite'
                result[badge.id] = -1
            else:
                result[badge.id] = badge.rule_max_number - badge.stat_my_monthly_sending
        return result

    _columns = {
        'name': fields.char('Badge', required=True, translate=True),
        'description': fields.text('Description', translate=True),
        'image': fields.binary("Image", attachment=True,
            help="This field holds the image used for the badge, limited to 256x256"),
        'rule_auth': fields.selection([
                ('everyone', 'Everyone'),
                ('users', 'A selected list of users'),
                ('having', 'People having some badges'),
                ('nobody', 'No one, assigned through challenges'),
            ],
            string="Allowance to Grant",
            help="Who can grant this badge",
            required=True),
        'rule_auth_user_ids': fields.many2many('res.users', 'rel_badge_auth_users',
            string='Authorized Users',
            help="Only these people can give this badge"),
        'rule_auth_badge_ids': fields.many2many('gamification.badge',
            'gamification_badge_rule_badge_rel', 'badge1_id', 'badge2_id',
            string='Required Badges',
            help="Only the people having these badges can give this badge"),

        'rule_max': fields.boolean('Monthly Limited Sending',
            help="Check to set a monthly limit per person of sending this badge"),
        'rule_max_number': fields.integer('Limitation Number',
            help="The maximum number of time this badge can be sent per month per person."),
        'stat_my_monthly_sending': fields.function(_get_badge_user_stats,
            type="integer",
            string='My Monthly Sending Total',
            multi='badge_users',
            help="The number of time the current user has sent this badge this month."),
        'remaining_sending': fields.function(_remaining_sending_calc, type='integer',
            string='Remaining Sending Allowed', help="If a maxium is set"),

        'challenge_ids': fields.one2many('gamification.challenge', 'reward_id',
            string="Reward of Challenges"),

        'goal_definition_ids': fields.many2many('gamification.goal.definition', 'badge_unlocked_definition_rel',
            string='Rewarded by',
            help="The users that have succeeded theses goals will receive automatically the badge."),

        'owner_ids': fields.one2many('gamification.badge.user', 'badge_id',
            string='Owners', help='The list of instances of this badge granted to users'),
        'active': fields.boolean('Active'),
        'unique_owner_ids': fields.function(_get_owners_info,
            string='Unique Owners',
            help="The list of unique users having received this badge.",
            multi='unique_users',
            type="many2many", relation="res.users"),

        'stat_count': fields.function(_get_owners_info, string='Total',
            type="integer",
            multi='unique_users',
            help="The number of time this badge has been received."),
        'stat_count_distinct': fields.function(_get_owners_info,
            type="integer",
            string='Number of users',
            multi='unique_users',
            help="The number of time this badge has been received by unique users."),
        'stat_this_month': fields.function(_get_badge_user_stats,
            type="integer",
            string='Monthly total',
            multi='badge_users',
            help="The number of time this badge has been received this month."),
        'stat_my': fields.function(_get_badge_user_stats, string='My Total',
            type="integer",
            multi='badge_users',
            help="The number of time the current user has received this badge."),
        'stat_my_this_month': fields.function(_get_badge_user_stats,
            type="integer",
            string='My Monthly Total',
            multi='badge_users',
            help="The number of time the current user has received this badge this month."),
    }

    _defaults = {
        'rule_auth': 'everyone',
        'active': True,
    }

    def check_granting(self, cr, uid, badge_id, context=None):
        """Check the user 'uid' can grant the badge 'badge_id' and raise the appropriate exception
        if not

        Do not check for SUPERUSER_ID
        """
        status_code = self._can_grant_badge(cr, uid, badge_id, context=context)
        if status_code == self.CAN_GRANT:
            return True
        elif status_code == self.NOBODY_CAN_GRANT:
            raise UserError(_('This badge can not be sent by users.'))
        elif status_code == self.USER_NOT_VIP:
            raise UserError(_('You are not in the user allowed list.'))
        elif status_code == self.BADGE_REQUIRED:
            raise UserError(_('You do not have the required badges.'))
        elif status_code == self.TOO_MANY:
            raise UserError(_('You have already sent this badge too many time this month.'))
        else:
            _logger.exception("Unknown badge status code: %d" % int(status_code))
        return False

    def _can_grant_badge(self, cr, uid, badge_id, context=None):
        """Check if a user can grant a badge to another user

        :param uid: the id of the res.users trying to send the badge
        :param badge_id: the granted badge id
        :return: integer representing the permission.
        """
        if uid == SUPERUSER_ID:
            return self.CAN_GRANT

        badge = self.browse(cr, uid, badge_id, context=context)

        if badge.rule_auth == 'nobody':
            return self.NOBODY_CAN_GRANT

        elif badge.rule_auth == 'users' and uid not in [user.id for user in badge.rule_auth_user_ids]:
            return self.USER_NOT_VIP

        elif badge.rule_auth == 'having':
            all_user_badges = self.pool.get('gamification.badge.user').search(cr, uid, [('user_id', '=', uid)], context=context)
            for required_badge in badge.rule_auth_badge_ids:
                if required_badge.id not in all_user_badges:
                    return self.BADGE_REQUIRED

        if badge.rule_max and badge.stat_my_monthly_sending >= badge.rule_max_number:
            return self.TOO_MANY

        # badge.rule_auth == 'everyone' -> no check
        return self.CAN_GRANT

    def check_progress(self, cr, uid, context=None):
        try:
            model, res_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'badge_hidden')
        except ValueError:
            return True
        badge_user_obj = self.pool.get('gamification.badge.user')
        if not badge_user_obj.search(cr, uid, [('user_id', '=', uid), ('badge_id', '=', res_id)], context=context):
            values = {
                'user_id': uid,
                'badge_id': res_id,
            }
            badge_user_obj.create(cr, SUPERUSER_ID, values, context=context)
        return True
Beispiel #14
0
class stock_landed_cost(osv.osv):
    _name = 'stock.landed.cost'
    _description = 'Stock Landed Cost'
    _inherit = 'mail.thread'

    def _total_amount(self, cr, uid, ids, name, args, context=None):
        result = {}
        for cost in self.browse(cr, uid, ids, context=context):
            total = 0.0
            for line in cost.cost_lines:
                total += line.price_unit
            result[cost.id] = total
        return result

    def _get_cost_line(self, cr, uid, ids, context=None):
        cost_to_recompute = []
        for line in self.pool.get('stock.landed.cost.lines').browse(
                cr, uid, ids, context=context):
            cost_to_recompute.append(line.cost_id.id)
        return cost_to_recompute

    def get_valuation_lines(self,
                            cr,
                            uid,
                            ids,
                            picking_ids=None,
                            context=None):
        picking_obj = self.pool.get('stock.picking')
        lines = []
        if not picking_ids:
            return lines

        for picking in picking_obj.browse(cr, uid, picking_ids):
            for move in picking.move_lines:
                #it doesn't make sense to make a landed cost for a product that isn't set as being valuated in real time at real cost
                if move.product_id.valuation != 'real_time' or move.product_id.cost_method != 'real':
                    continue
                total_cost = 0.0
                weight = move.product_id and move.product_id.weight * move.product_qty
                volume = move.product_id and move.product_id.volume * move.product_qty
                for quant in move.quant_ids:
                    total_cost += quant.cost * quant.qty
                vals = dict(product_id=move.product_id.id,
                            move_id=move.id,
                            quantity=move.product_uom_qty,
                            former_cost=total_cost,
                            weight=weight,
                            volume=volume)
                lines.append(vals)
        if not lines:
            raise UserError(
                _('The selected picking does not contain any move that would be impacted by landed costs. Landed costs are only possible for products configured in real time valuation with real price costing method. Please make sure it is the case, or you selected the correct picking'
                  ))
        return lines

    _columns = {
        'name':
        fields.char('Name',
                    track_visibility='always',
                    readonly=True,
                    copy=False),
        'date':
        fields.date('Date',
                    required=True,
                    states={'done': [('readonly', True)]},
                    track_visibility='onchange',
                    copy=False),
        'picking_ids':
        fields.many2many('stock.picking',
                         string='Pickings',
                         states={'done': [('readonly', True)]},
                         copy=False),
        'cost_lines':
        fields.one2many('stock.landed.cost.lines',
                        'cost_id',
                        'Cost Lines',
                        states={'done': [('readonly', True)]},
                        copy=True),
        'valuation_adjustment_lines':
        fields.one2many('stock.valuation.adjustment.lines',
                        'cost_id',
                        'Valuation Adjustments',
                        states={'done': [('readonly', True)]}),
        'description':
        fields.text('Item Description', states={'done': [('readonly', True)]}),
        'amount_total':
        fields.function(_total_amount,
                        type='float',
                        string='Total',
                        digits=0,
                        store={
                            'stock.landed.cost':
                            (lambda self, cr, uid, ids, c={}: ids,
                             ['cost_lines'], 20),
                            'stock.landed.cost.lines':
                            (_get_cost_line,
                             ['price_unit', 'quantity', 'cost_id'], 20),
                        },
                        track_visibility='always'),
        'state':
        fields.selection([('draft', 'Draft'), ('done', 'Posted'),
                          ('cancel', 'Cancelled')],
                         'State',
                         readonly=True,
                         track_visibility='onchange',
                         copy=False),
        'account_move_id':
        fields.many2one('account.move',
                        'Journal Entry',
                        readonly=True,
                        copy=False),
        'account_journal_id':
        fields.many2one('account.journal',
                        'Account Journal',
                        required=True,
                        states={'done': [('readonly', True)]}),
    }

    _defaults = {
        'name':
        lambda obj, cr, uid, context: obj.pool.get('ir.sequence').next_by_code(
            cr, uid, 'stock.landed.cost'),
        'state':
        'draft',
        'date':
        fields.date.context_today,
    }

    def _create_accounting_entries(self,
                                   cr,
                                   uid,
                                   line,
                                   move_id,
                                   qty_out,
                                   context=None):
        product_obj = self.pool.get('product.template')
        cost_product = line.cost_line_id and line.cost_line_id.product_id
        if not cost_product:
            return False
        accounts = product_obj.browse(cr,
                                      uid,
                                      line.product_id.product_tmpl_id.id,
                                      context=context).get_product_accounts()
        debit_account_id = accounts.get(
            'stock_valuation',
            False) and accounts['stock_valuation'].id or False
        already_out_account_id = accounts['stock_output'].id
        credit_account_id = line.cost_line_id.account_id.id or cost_product.property_account_expense_id.id or cost_product.categ_id.property_account_expense_categ_id.id

        if not credit_account_id:
            raise UserError(
                _('Please configure Stock Expense Account for product: %s.') %
                (cost_product.name))

        return self._create_account_move_line(cr,
                                              uid,
                                              line,
                                              move_id,
                                              credit_account_id,
                                              debit_account_id,
                                              qty_out,
                                              already_out_account_id,
                                              context=context)

    def _create_account_move_line(self,
                                  cr,
                                  uid,
                                  line,
                                  move_id,
                                  credit_account_id,
                                  debit_account_id,
                                  qty_out,
                                  already_out_account_id,
                                  context=None):
        """
        Generate the account.move.line values to track the landed cost.
        Afterwards, for the goods that are already out of stock, we should create the out moves
        """
        aml_obj = self.pool.get('account.move.line')
        if context is None:
            context = {}
        ctx = context.copy()
        ctx['check_move_validity'] = False
        base_line = {
            'name': line.name,
            'move_id': move_id,
            'product_id': line.product_id.id,
            'quantity': line.quantity,
        }
        debit_line = dict(base_line, account_id=debit_account_id)
        credit_line = dict(base_line, account_id=credit_account_id)
        diff = line.additional_landed_cost
        if diff > 0:
            debit_line['debit'] = diff
            credit_line['credit'] = diff
        else:
            # negative cost, reverse the entry
            debit_line['credit'] = -diff
            credit_line['debit'] = -diff
        aml_obj.create(cr, uid, debit_line, context=ctx)
        aml_obj.create(cr, uid, credit_line, context=ctx)

        #Create account move lines for quants already out of stock
        if qty_out > 0:
            debit_line = dict(debit_line,
                              name=(line.name + ": " + str(qty_out) +
                                    _(' already out')),
                              quantity=qty_out,
                              account_id=already_out_account_id)
            credit_line = dict(credit_line,
                               name=(line.name + ": " + str(qty_out) +
                                     _(' already out')),
                               quantity=qty_out,
                               account_id=debit_account_id)
            diff = diff * qty_out / line.quantity
            if diff > 0:
                debit_line['debit'] = diff
                credit_line['credit'] = diff
            else:
                # negative cost, reverse the entry
                debit_line['credit'] = -diff
                credit_line['debit'] = -diff
            aml_obj.create(cr, uid, debit_line, context=ctx)
            aml_obj.create(cr, uid, credit_line, context=ctx)
        self.pool.get('account.move').assert_balanced(cr,
                                                      uid, [move_id],
                                                      context=context)
        return True

    def _create_account_move(self, cr, uid, cost, context=None):
        vals = {
            'journal_id': cost.account_journal_id.id,
            'date': cost.date,
            'ref': cost.name
        }
        return self.pool.get('account.move').create(cr,
                                                    uid,
                                                    vals,
                                                    context=context)

    def _check_sum(self, cr, uid, landed_cost, context=None):
        """
        Will check if each cost line its valuation lines sum to the correct amount
        and if the overall total amount is correct also
        """
        costcor = {}
        tot = 0
        for valuation_line in landed_cost.valuation_adjustment_lines:
            if costcor.get(valuation_line.cost_line_id):
                costcor[valuation_line.
                        cost_line_id] += valuation_line.additional_landed_cost
            else:
                costcor[valuation_line.
                        cost_line_id] = valuation_line.additional_landed_cost
            tot += valuation_line.additional_landed_cost

        prec = self.pool['decimal.precision'].precision_get(cr, uid, 'Account')
        # float_compare returns 0 for equal amounts
        res = not bool(
            float_compare(tot, landed_cost.amount_total,
                          precision_digits=prec))
        for costl in costcor.keys():
            if float_compare(costcor[costl],
                             costl.price_unit,
                             precision_digits=prec):
                res = False
        return res

    def button_validate(self, cr, uid, ids, context=None):
        quant_obj = self.pool.get('stock.quant')

        for cost in self.browse(cr, uid, ids, context=context):
            if cost.state != 'draft':
                raise UserError(_('Only draft landed costs can be validated'))
            if not cost.valuation_adjustment_lines or not self._check_sum(
                    cr, uid, cost, context=context):
                raise UserError(
                    _('You cannot validate a landed cost which has no valid valuation adjustments lines. Did you click on Compute?'
                      ))
            move_id = self._create_account_move(cr, uid, cost, context=context)
            for line in cost.valuation_adjustment_lines:
                if not line.move_id:
                    continue
                per_unit = line.final_cost / line.quantity
                diff = per_unit - line.former_cost_per_unit
                quants = [quant for quant in line.move_id.quant_ids]
                quant_dict = {}
                for quant in quants:
                    if quant.id not in quant_dict:
                        quant_dict[quant.id] = quant.cost + diff
                    else:
                        quant_dict[quant.id] += diff
                for key, value in quant_dict.items():
                    quant_obj.write(cr,
                                    SUPERUSER_ID,
                                    key, {'cost': value},
                                    context=context)
                qty_out = 0
                for quant in line.move_id.quant_ids:
                    if quant.location_id.usage != 'internal':
                        qty_out += quant.qty
                self._create_accounting_entries(cr,
                                                uid,
                                                line,
                                                move_id,
                                                qty_out,
                                                context=context)
            self.write(cr,
                       uid,
                       cost.id, {
                           'state': 'done',
                           'account_move_id': move_id
                       },
                       context=context)
            self.pool.get('account.move').post(cr,
                                               uid, [move_id],
                                               context=context)
        return True

    def button_cancel(self, cr, uid, ids, context=None):
        cost = self.browse(cr, uid, ids, context=context)
        if cost.state == 'done':
            raise UserError(
                _('Validated landed costs cannot be cancelled, '
                  'but you could create negative landed costs to reverse them')
            )
        return cost.write({'state': 'cancel'})

    def unlink(self, cr, uid, ids, context=None):
        # cancel or raise first
        self.button_cancel(cr, uid, ids, context)
        return super(stock_landed_cost, self).unlink(cr,
                                                     uid,
                                                     ids,
                                                     context=context)

    def compute_landed_cost(self, cr, uid, ids, context=None):
        line_obj = self.pool.get('stock.valuation.adjustment.lines')
        unlink_ids = line_obj.search(cr,
                                     uid, [('cost_id', 'in', ids)],
                                     context=context)
        line_obj.unlink(cr, uid, unlink_ids, context=context)
        digits = dp.get_precision('Product Price')(cr)
        towrite_dict = {}
        for cost in self.browse(cr, uid, ids, context=None):
            if not cost.picking_ids:
                continue
            picking_ids = [p.id for p in cost.picking_ids]
            total_qty = 0.0
            total_cost = 0.0
            total_weight = 0.0
            total_volume = 0.0
            total_line = 0.0
            vals = self.get_valuation_lines(cr,
                                            uid, [cost.id],
                                            picking_ids=picking_ids,
                                            context=context)
            for v in vals:
                for line in cost.cost_lines:
                    v.update({'cost_id': cost.id, 'cost_line_id': line.id})
                    self.pool.get('stock.valuation.adjustment.lines').create(
                        cr, uid, v, context=context)
                total_qty += v.get('quantity', 0.0)
                total_cost += v.get('former_cost', 0.0)
                total_weight += v.get('weight', 0.0)
                total_volume += v.get('volume', 0.0)
                total_line += 1

            for line in cost.cost_lines:
                value_split = 0.0
                for valuation in cost.valuation_adjustment_lines:
                    value = 0.0
                    if valuation.cost_line_id and valuation.cost_line_id.id == line.id:
                        if line.split_method == 'by_quantity' and total_qty:
                            per_unit = (line.price_unit / total_qty)
                            value = valuation.quantity * per_unit
                        elif line.split_method == 'by_weight' and total_weight:
                            per_unit = (line.price_unit / total_weight)
                            value = valuation.weight * per_unit
                        elif line.split_method == 'by_volume' and total_volume:
                            per_unit = (line.price_unit / total_volume)
                            value = valuation.volume * per_unit
                        elif line.split_method == 'equal':
                            value = (line.price_unit / total_line)
                        elif line.split_method == 'by_current_cost_price' and total_cost:
                            per_unit = (line.price_unit / total_cost)
                            value = valuation.former_cost * per_unit
                        else:
                            value = (line.price_unit / total_line)

                        if digits:
                            value = float_round(value,
                                                precision_digits=digits[1],
                                                rounding_method='UP')
                            fnc = min if line.price_unit > 0 else max
                            value = fnc(value, line.price_unit - value_split)
                            value_split += value

                        if valuation.id not in towrite_dict:
                            towrite_dict[valuation.id] = value
                        else:
                            towrite_dict[valuation.id] += value
        if towrite_dict:
            for key, value in towrite_dict.items():
                line_obj.write(cr,
                               uid,
                               key, {'additional_landed_cost': value},
                               context=context)
        return True

    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 == 'done':
            return 'stock_landed_costs.mt_stock_landed_cost_open'
        return super(stock_landed_cost, self)._track_subtype(cr,
                                                             uid,
                                                             ids,
                                                             init_values,
                                                             context=context)
Beispiel #15
0
class mrp_repair(osv.osv):
    _name = 'mrp.repair'
    _inherit = 'mail.thread'
    _description = 'Repair Order'

    def _amount_untaxed(self, cr, uid, ids, field_name, arg, context=None):
        """ Calculates untaxed amount.
        @param self: The object pointer
        @param cr: The current row, from the database cursor,
        @param uid: The current user ID for security checks
        @param ids: List of selected IDs
        @param field_name: Name of field.
        @param arg: Argument
        @param context: A standard dictionary for contextual values
        @return: Dictionary of values.
        """
        res = {}
        cur_obj = self.pool.get('res.currency')

        for repair in self.browse(cr, uid, ids, context=context):
            res[repair.id] = 0.0
            for line in repair.operations:
                res[repair.id] += line.price_subtotal
            for line in repair.fees_lines:
                res[repair.id] += line.price_subtotal
            cur = repair.pricelist_id.currency_id
            res[repair.id] = cur_obj.round(cr, uid, cur, res[repair.id])
        return res

    def _amount_tax(self, cr, uid, ids, field_name, arg, context=None):
        """ Calculates taxed amount.
        @param field_name: Name of field.
        @param arg: Argument
        @return: Dictionary of values.
        """
        res = {}
        #return {}.fromkeys(ids, 0)
        cur_obj = self.pool.get('res.currency')
        tax_obj = self.pool.get('account.tax')
        for repair in self.browse(cr, uid, ids, context=context):
            val = 0.0
            cur = repair.pricelist_id.currency_id
            for line in repair.operations:
                #manage prices with tax included use compute_all instead of compute
                if line.to_invoice and line.tax_id:
                    tax_calculate = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, cur, line.product_uom_qty, line.product_id.id, repair.partner_id.id)
                    for c in tax_calculate['taxes']:
                        val += c['amount']
            for line in repair.fees_lines:
                if line.to_invoice and line.tax_id:
                    tax_calculate = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, cur, line.product_uom_qty, line.product_id.id, repair.partner_id.id)
                    for c in tax_calculate['taxes']:
                        val += c['amount']
            res[repair.id] = cur_obj.round(cr, uid, cur, val)
        return res

    def _amount_total(self, cr, uid, ids, field_name, arg, context=None):
        """ Calculates total amount.
        @param field_name: Name of field.
        @param arg: Argument
        @return: Dictionary of values.
        """
        res = {}
        untax = self._amount_untaxed(cr, uid, ids, field_name, arg, context=context)
        tax = self._amount_tax(cr, uid, ids, field_name, arg, context=context)
        cur_obj = self.pool.get('res.currency')
        for id in ids:
            repair = self.browse(cr, uid, id, context=context)
            cur = repair.pricelist_id.currency_id
            res[id] = cur_obj.round(cr, uid, cur, untax.get(id, 0.0) + tax.get(id, 0.0))
        return res

    def _get_default_address(self, cr, uid, ids, field_name, arg, context=None):
        res = {}
        partner_obj = self.pool.get('res.partner')
        for data in self.browse(cr, uid, ids, context=context):
            adr_id = False
            if data.partner_id:
                adr_id = partner_obj.address_get(cr, uid, [data.partner_id.id], ['contact'])['contact']
            res[data.id] = adr_id
        return res

    def _get_lines(self, cr, uid, ids, context=None):
        return self.pool['mrp.repair'].search(cr, uid, [('operations', 'in', ids)], context=context)

    def _get_fee_lines(self, cr, uid, ids, context=None):
        return self.pool['mrp.repair'].search(cr, uid, [('fees_lines', 'in', ids)], context=context)

    _columns = {
        'name': fields.char('Repair Reference', required=True, states={'confirmed': [('readonly', True)]}, copy=False),
        'product_id': fields.many2one('product.product', string='Product to Repair', required=True, readonly=True, states={'draft': [('readonly', False)]}),
        'product_qty': fields.float('Product Quantity', digits_compute=dp.get_precision('Product Unit of Measure'),
                                    required=True, readonly=True, states={'draft': [('readonly', False)]}),
        'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}),
        'partner_id': fields.many2one('res.partner', 'Partner', select=True, help='Choose partner for whom the order will be invoiced and delivered.', states={'confirmed': [('readonly', True)]}),
        'address_id': fields.many2one('res.partner', 'Delivery Address', domain="[('parent_id','=',partner_id)]", states={'confirmed': [('readonly', True)]}),
        'default_address_id': fields.function(_get_default_address, type="many2one", relation="res.partner"),
        'state': fields.selection([
            ('draft', 'Quotation'),
            ('cancel', 'Cancelled'),
            ('confirmed', 'Confirmed'),
            ('under_repair', 'Under Repair'),
            ('ready', 'Ready to Repair'),
            ('2binvoiced', 'To be Invoiced'),
            ('invoice_except', 'Invoice Exception'),
            ('done', 'Repaired')
            ], 'Status', readonly=True, track_visibility='onchange', copy=False,
            help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed repair order. \
            \n* The \'Confirmed\' status is used when a user confirms the repair order. \
            \n* The \'Ready to Repair\' status is used to start to repairing, user can start repairing only after repair order is confirmed. \
            \n* The \'To be Invoiced\' status is used to generate the invoice before or after repairing done. \
            \n* The \'Done\' status is set when repairing is completed.\
            \n* The \'Cancelled\' status is used when user cancel repair order.'),
        'location_id': fields.many2one('stock.location', 'Current Location', select=True, required=True, readonly=True, states={'draft': [('readonly', False)], 'confirmed': [('readonly', True)]}),
        'location_dest_id': fields.many2one('stock.location', 'Delivery Location', readonly=True, required=True, states={'draft': [('readonly', False)], 'confirmed': [('readonly', True)]}),
        'lot_id': fields.many2one('stock.production.lot', 'Repaired Lot', domain="[('product_id','=', product_id)]", help="Products repaired are all belonging to this lot", oldname="prodlot_id"),
        'guarantee_limit': fields.date('Warranty Expiration', states={'confirmed': [('readonly', True)]}),
        'operations': fields.one2many('mrp.repair.line', 'repair_id', 'Operation Lines', readonly=True, states={'draft': [('readonly', False)]}, copy=True),
        'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', help='Pricelist of the selected partner.'),
        'partner_invoice_id': fields.many2one('res.partner', 'Invoicing Address'),
        'invoice_method': fields.selection([
            ("none", "No Invoice"),
            ("b4repair", "Before Repair"),
            ("after_repair", "After Repair")
           ], "Invoice Method",
            select=True, required=True, states={'draft': [('readonly', False)]}, readonly=True, help='Selecting \'Before Repair\' or \'After Repair\' will allow you to generate invoice before or after the repair is done respectively. \'No invoice\' means you don\'t want to generate invoice for this repair order.'),
        'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True, track_visibility="onchange", copy=False),
        'move_id': fields.many2one('stock.move', 'Move', readonly=True, help="Move created by the repair order", track_visibility="onchange", copy=False),
        'fees_lines': fields.one2many('mrp.repair.fee', 'repair_id', 'Fees', readonly=True, states={'draft': [('readonly', False)]}, copy=True),
        'internal_notes': fields.text('Internal Notes'),
        'quotation_notes': fields.text('Quotation Notes'),
        'company_id': fields.many2one('res.company', 'Company'),
        'invoiced': fields.boolean('Invoiced', readonly=True, copy=False),
        'repaired': fields.boolean('Repaired', readonly=True, copy=False),
        'amount_untaxed': fields.function(_amount_untaxed, string='Untaxed Amount',
            store={
                'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10),
                'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
                'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
            }),
        'amount_tax': fields.function(_amount_tax, string='Taxes',
            store={
                'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10),
                'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
                'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
            }),
        'amount_total': fields.function(_amount_total, string='Total',
            store={
                'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10),
                'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
                'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
            }),
    }

    def _default_stock_location(self, cr, uid, context=None):
        try:
            warehouse = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'warehouse0')
            return warehouse.lot_stock_id.id
        except:
            return False

    _defaults = {
        'state': lambda *a: 'draft',
        'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').next_by_code(cr, uid, 'mrp.repair'),
        'invoice_method': lambda *a: 'none',
        'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.repair', context=context),
        'pricelist_id': lambda self, cr, uid, context: self.pool['product.pricelist'].search(cr, uid, [], limit=1)[0],
        'product_qty': 1.0,
        'location_id': _default_stock_location,
    }

    _sql_constraints = [
        ('name', 'unique (name)', 'The name of the Repair Order must be unique!'),
    ]

    def onchange_product_id(self, cr, uid, ids, product_id=None):
        """ On change of product sets some values.
        @param product_id: Changed product
        @return: Dictionary of values.
        """
        product = False
        if product_id:
            product = self.pool.get("product.product").browse(cr, uid, product_id)
        return {'value': {
                    'guarantee_limit': False,
                    'lot_id': False,
                    'product_uom': product and product.uom_id.id or False,
                }
        }

    def onchange_product_uom(self, cr, uid, ids, product_id, product_uom, context=None):
        res = {'value': {}}
        if not product_uom or not product_id:
            return res
        product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
        uom = self.pool.get('product.uom').browse(cr, uid, product_uom, context=context)
        if uom.category_id.id != product.uom_id.category_id.id:
            res['warning'] = {'title': _('Warning'), 'message': _('The Product Unit of Measure you chose has a different category than in the product form.')}
            res['value'].update({'product_uom': product.uom_id.id})
        return res

    def onchange_location_id(self, cr, uid, ids, location_id=None):
        """ On change of location
        """
        return {'value': {'location_dest_id': location_id}}

    def button_dummy(self, cr, uid, ids, context=None):
        return True

    def onchange_partner_id(self, cr, uid, ids, part, address_id):
        """ On change of partner sets the values of partner address,
        partner invoice address and pricelist.
        @param part: Changed id of partner.
        @param address_id: Address id from current record.
        @return: Dictionary of values.
        """
        part_obj = self.pool.get('res.partner')
        pricelist_obj = self.pool.get('product.pricelist')
        if not part:
            return {'value': {
                        'address_id': False,
                        'partner_invoice_id': False,
                        'pricelist_id': pricelist_obj.search(cr, uid, [], limit=1)[0]
                    }
            }
        addr = part_obj.address_get(cr, uid, [part], ['delivery', 'invoice', 'contact'])
        partner = part_obj.browse(cr, uid, part)
        pricelist = partner.property_product_pricelist and partner.property_product_pricelist.id or False
        return {'value': {
                    'address_id': addr['delivery'] or addr['contact'],
                    'partner_invoice_id': addr['invoice'],
                    'pricelist_id': pricelist
                }
        }

    def action_cancel_draft(self, cr, uid, ids, *args):
        """ Cancels repair order when it is in 'Draft' state.
        @param *arg: Arguments
        @return: True
        """
        if not len(ids):
            return False
        mrp_line_obj = self.pool.get('mrp.repair.line')
        for repair in self.browse(cr, uid, ids):
            mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'draft'})
        self.write(cr, uid, ids, {'state': 'draft'})
        return self.create_workflow(cr, uid, ids)

    def action_confirm(self, cr, uid, ids, *args):
        """ Repair order state is set to 'To be invoiced' when invoice method
        is 'Before repair' else state becomes 'Confirmed'.
        @param *arg: Arguments
        @return: True
        """
        mrp_line_obj = self.pool.get('mrp.repair.line')
        for o in self.browse(cr, uid, ids):
            if (o.invoice_method == 'b4repair'):
                self.write(cr, uid, [o.id], {'state': '2binvoiced'})
            else:
                self.write(cr, uid, [o.id], {'state': 'confirmed'})
                for line in o.operations:
                    if line.product_id.tracking != 'none' and not line.lot_id:
                        raise UserError(_("Serial number is required for operation line with product '%s'") % (line.product_id.name))
                mrp_line_obj.write(cr, uid, [l.id for l in o.operations], {'state': 'confirmed'})
        return True

    def action_cancel(self, cr, uid, ids, context=None):
        """ Cancels repair order.
        @return: True
        """
        mrp_line_obj = self.pool.get('mrp.repair.line')
        for repair in self.browse(cr, uid, ids, context=context):
            if not repair.invoiced:
                mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'cancel'}, context=context)
            else:
                raise UserError(_('Repair order is already invoiced.'))
        return self.write(cr, uid, ids, {'state': 'cancel'})

    def wkf_invoice_create(self, cr, uid, ids, *args):
        self.action_invoice_create(cr, uid, ids)
        return True

    def action_invoice_create(self, cr, uid, ids, group=False, context=None):
        """ Creates invoice(s) for repair order.
        @param group: It is set to true when group invoice is to be generated.
        @return: Invoice Ids.
        """
        res = {}
        invoices_group = {}
        inv_line_obj = self.pool.get('account.invoice.line')
        inv_obj = self.pool.get('account.invoice')
        repair_line_obj = self.pool.get('mrp.repair.line')
        repair_fee_obj = self.pool.get('mrp.repair.fee')
        for repair in self.browse(cr, uid, ids, context=context):
            res[repair.id] = False
            if repair.state in ('draft', 'cancel') or repair.invoice_id:
                continue
            if not (repair.partner_id.id and repair.partner_invoice_id.id):
                raise UserError(_('You have to select a Partner Invoice Address in the repair form!'))
            comment = repair.quotation_notes
            if (repair.invoice_method != 'none'):
                if group and repair.partner_invoice_id.id in invoices_group:
                    inv_id = invoices_group[repair.partner_invoice_id.id]
                    invoice = inv_obj.browse(cr, uid, inv_id)
                    invoice_vals = {
                        'name': invoice.name + ', ' + repair.name,
                        'origin': invoice.origin + ', ' + repair.name,
                        'comment': (comment and (invoice.comment and invoice.comment + "\n" + comment or comment)) or (invoice.comment and invoice.comment or ''),
                    }
                    inv_obj.write(cr, uid, [inv_id], invoice_vals, context=context)
                else:
                    if not repair.partner_id.property_account_receivable_id:
                        raise UserError(_('No account defined for partner "%s".') % repair.partner_id.name)
                    account_id = repair.partner_id.property_account_receivable_id.id
                    inv = {
                        'name': repair.name,
                        'origin': repair.name,
                        'type': 'out_invoice',
                        'account_id': account_id,
                        'partner_id': repair.partner_invoice_id.id or repair.partner_id.id,
                        'currency_id': repair.pricelist_id.currency_id.id,
                        'comment': repair.quotation_notes,
                        'fiscal_position_id': repair.partner_id.property_account_position_id.id
                    }
                    inv_id = inv_obj.create(cr, uid, inv)
                    invoices_group[repair.partner_invoice_id.id] = inv_id
                self.write(cr, uid, repair.id, {'invoiced': True, 'invoice_id': inv_id})

                for operation in repair.operations:
                    if operation.to_invoice:
                        if group:
                            name = repair.name + '-' + operation.name
                        else:
                            name = operation.name

                        if operation.product_id.property_account_income_id:
                            account_id = operation.product_id.property_account_income_id.id
                        elif operation.product_id.categ_id.property_account_income_categ_id:
                            account_id = operation.product_id.categ_id.property_account_income_categ_id.id
                        else:
                            raise UserError(_('No account defined for product "%s".') % operation.product_id.name)

                        invoice_line_id = inv_line_obj.create(cr, uid, {
                            'invoice_id': inv_id,
                            'name': name,
                            'origin': repair.name,
                            'account_id': account_id,
                            'quantity': operation.product_uom_qty,
                            'invoice_line_tax_ids': [(6, 0, [x.id for x in operation.tax_id])],
                            'uom_id': operation.product_uom.id,
                            'price_unit': operation.price_unit,
                            'price_subtotal': operation.product_uom_qty * operation.price_unit,
                            'product_id': operation.product_id and operation.product_id.id or False
                        })
                        repair_line_obj.write(cr, uid, [operation.id], {'invoiced': True, 'invoice_line_id': invoice_line_id})
                for fee in repair.fees_lines:
                    if fee.to_invoice:
                        if group:
                            name = repair.name + '-' + fee.name
                        else:
                            name = fee.name
                        if not fee.product_id:
                            raise UserError(_('No product defined on Fees!'))

                        if fee.product_id.property_account_income_id:
                            account_id = fee.product_id.property_account_income_id.id
                        elif fee.product_id.categ_id.property_account_income_categ_id:
                            account_id = fee.product_id.categ_id.property_account_income_categ_id.id
                        else:
                            raise UserError(_('No account defined for product "%s".') % fee.product_id.name)

                        invoice_fee_id = inv_line_obj.create(cr, uid, {
                            'invoice_id': inv_id,
                            'name': name,
                            'origin': repair.name,
                            'account_id': account_id,
                            'quantity': fee.product_uom_qty,
                            'invoice_line_tax_ids': [(6, 0, [x.id for x in fee.tax_id])],
                            'uom_id': fee.product_uom.id,
                            'product_id': fee.product_id and fee.product_id.id or False,
                            'price_unit': fee.price_unit,
                            'price_subtotal': fee.product_uom_qty * fee.price_unit
                        })
                        repair_fee_obj.write(cr, uid, [fee.id], {'invoiced': True, 'invoice_line_id': invoice_fee_id})
                #inv_obj.button_reset_taxes(cr, uid, inv_id, context=context)
                res[repair.id] = inv_id
        return res

    def action_repair_ready(self, cr, uid, ids, context=None):
        """ Writes repair order state to 'Ready'
        @return: True
        """
        for repair in self.browse(cr, uid, ids, context=context):
            self.pool.get('mrp.repair.line').write(cr, uid, [l.id for
                    l in repair.operations], {'state': 'confirmed'}, context=context)
            self.write(cr, uid, [repair.id], {'state': 'ready'})
        return True

    def action_repair_start(self, cr, uid, ids, context=None):
        """ Writes repair order state to 'Under Repair'
        @return: True
        """
        repair_line = self.pool.get('mrp.repair.line')
        for repair in self.browse(cr, uid, ids, context=context):
            repair_line.write(cr, uid, [l.id for
                    l in repair.operations], {'state': 'confirmed'}, context=context)
            repair.write({'state': 'under_repair'})
        return True

    def action_repair_end(self, cr, uid, ids, context=None):
        """ Writes repair order state to 'To be invoiced' if invoice method is
        After repair else state is set to 'Ready'.
        @return: True
        """
        for order in self.browse(cr, uid, ids, context=context):
            val = {}
            val['repaired'] = True
            if (not order.invoiced and order.invoice_method == 'after_repair'):
                val['state'] = '2binvoiced'
            elif (not order.invoiced and order.invoice_method == 'b4repair'):
                val['state'] = 'ready'
            else:
                pass
            self.write(cr, uid, [order.id], val)
        return True

    def wkf_repair_done(self, cr, uid, ids, *args):
        self.action_repair_done(cr, uid, ids)
        return True

    def action_repair_done(self, cr, uid, ids, context=None):
        """ Creates stock move for operation and stock move for final product of repair order.
        @return: Move ids of final products
        """
        res = {}
        move_obj = self.pool.get('stock.move')
        repair_line_obj = self.pool.get('mrp.repair.line')
        for repair in self.browse(cr, uid, ids, context=context):
            move_ids = []
            for move in repair.operations:
                move_id = move_obj.create(cr, uid, {
                    'name': move.name,
                    'product_id': move.product_id.id,
                    'restrict_lot_id': move.lot_id.id,
                    'product_uom_qty': move.product_uom_qty,
                    'product_uom': move.product_uom.id,
                    'partner_id': repair.address_id and repair.address_id.id or False,
                    'location_id': move.location_id.id,
                    'location_dest_id': move.location_dest_id.id,
                })
                move_ids.append(move_id)
                repair_line_obj.write(cr, uid, [move.id], {'move_id': move_id, 'state': 'done'}, context=context)
            move_id = move_obj.create(cr, uid, {
                'name': repair.name,
                'product_id': repair.product_id.id,
                'product_uom': repair.product_uom.id or repair.product_id.uom_id.id,
                'product_uom_qty': repair.product_qty,
                'partner_id': repair.address_id and repair.address_id.id or False,
                'location_id': repair.location_id.id,
                'location_dest_id': repair.location_dest_id.id,
                'restrict_lot_id': repair.lot_id.id,
            })
            move_ids.append(move_id)
            move_obj.action_done(cr, uid, move_ids, context=context)
            self.write(cr, uid, [repair.id], {'state': 'done', 'move_id': move_id}, context=context)
            res[repair.id] = move_id
        return res
Beispiel #16
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
Beispiel #17
0
class sale_order_option(osv.osv):
    _name = "sale.order.option"
    _description = "Sale Options"
    _columns = {
        'order_id':
        fields.many2one('sale.order',
                        'Sale Order Reference',
                        ondelete='cascade',
                        select=True),
        'line_id':
        fields.many2one('sale.order.line', on_delete="set null"),
        'name':
        fields.text('Description', required=True),
        'product_id':
        fields.many2one('product.product',
                        'Product',
                        domain=[('sale_ok', '=', True)]),
        'website_description':
        fields.html('Line Description'),
        'price_unit':
        fields.float('Unit Price',
                     required=True,
                     digits_compute=dp.get_precision('Product Price')),
        'discount':
        fields.float('Discount (%)',
                     digits_compute=dp.get_precision('Discount')),
        'uom_id':
        fields.many2one('product.uom', 'Unit of Measure ', required=True),
        'quantity':
        fields.float('Quantity',
                     required=True,
                     digits_compute=dp.get_precision('Product UoS')),
    }

    _defaults = {
        'quantity': 1,
    }

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

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

    @api.onchange('product_id', 'uom_id')
    def _onchange_product_id(self):
        if not self.product_id:
            return
        product = self.product_id.with_context(
            lang=self.order_id.partner_id.lang)
        self.price_unit = product.list_price
        self.website_description = product.quote_description or product.website_description
        self.name = product.name
        if product.description_sale:
            self.name += '\n' + product.description_sale
        self.uom_id = product.product_tmpl_id.uom_id
        if product and self.order_id.pricelist_id:
            partner_id = self.order_id.partner_id.id
            pricelist = self.order_id.pricelist_id.id
            self.price_unit = self.order_id.pricelist_id.price_get(
                product.id, self.quantity, partner_id)[pricelist]
        if self.uom_id and self.uom_id != self.product_id.uom_id:
            self.price_unit = self.product_id.uom_id._compute_price(
                self.price_unit, self.uom_id.id)
        domain = {
            'uom_id':
            [('category_id', '=', self.product_id.uom_id.category_id.id)]
        }
        return {'domain': domain}
Beispiel #18
0
class sale_quote_line(osv.osv):
    _name = "sale.quote.line"
    _description = "Quotation Template Lines"
    _columns = {
        'sequence':
        fields.integer(
            'Sequence',
            help=
            "Gives the sequence order when displaying a list of sale quote lines."
        ),
        'quote_id':
        fields.many2one('sale.quote.template',
                        'Quotation Template Reference',
                        required=True,
                        ondelete='cascade',
                        select=True),
        'name':
        fields.text('Description', required=True, translate=True),
        'product_id':
        fields.many2one('product.product',
                        'Product',
                        domain=[('sale_ok', '=', True)],
                        required=True),
        'website_description':
        fields.related('product_id',
                       'product_tmpl_id',
                       'quote_description',
                       string='Line Description',
                       type='html',
                       translate=True),
        'price_unit':
        fields.float('Unit Price',
                     required=True,
                     digits_compute=dp.get_precision('Product Price')),
        'discount':
        fields.float('Discount (%)',
                     digits_compute=dp.get_precision('Discount')),
        'product_uom_qty':
        fields.float('Quantity',
                     required=True,
                     digits_compute=dp.get_precision('Product UoS')),
        'product_uom_id':
        fields.many2one('product.uom', 'Unit of Measure ', required=True),
    }
    _order = 'sequence, id'
    _defaults = {
        'product_uom_qty': 1,
        'discount': 0.0,
        'sequence': 10,
    }

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

    def product_uom_change(self, cr, uid, ids, product, uom_id, context=None):
        context = context or {}
        if not uom_id:
            return {'value': {'price_unit': 0.0, 'uom_id': False}}
        return self.on_change_product_id(cr,
                                         uid,
                                         ids,
                                         product,
                                         uom_id=uom_id,
                                         context=context)

    def _inject_quote_description(self, cr, uid, values, context=None):
        values = dict(values or {})
        if not values.get('website_description') and values.get('product_id'):
            product = self.pool['product.product'].browse(cr,
                                                          uid,
                                                          values['product_id'],
                                                          context=context)
            values[
                'website_description'] = product.quote_description or product.website_description or ''
        return values

    def create(self, cr, uid, values, context=None):
        values = self._inject_quote_description(cr, uid, values, context)
        ret = super(sale_quote_line, self).create(cr,
                                                  uid,
                                                  values,
                                                  context=context)
        # hack because create don t make the job for a related field
        if values.get('website_description'):
            self.write(cr,
                       uid,
                       ret,
                       {'website_description': values['website_description']},
                       context=context)
        return ret

    def write(self, cr, uid, ids, values, context=None):
        values = self._inject_quote_description(cr, uid, values, context)
        return super(sale_quote_line, self).write(cr,
                                                  uid,
                                                  ids,
                                                  values,
                                                  context=context)
Beispiel #19
0
class sale_quote_option(osv.osv):
    _name = "sale.quote.option"
    _description = "Quotation Option"
    _columns = {
        'template_id':
        fields.many2one('sale.quote.template',
                        'Quotation Template Reference',
                        ondelete='cascade',
                        select=True,
                        required=True),
        'name':
        fields.text('Description', required=True, translate=True),
        'product_id':
        fields.many2one('product.product',
                        'Product',
                        domain=[('sale_ok', '=', True)],
                        required=True),
        'website_description':
        fields.html('Option Description', translate=True),
        'price_unit':
        fields.float('Unit Price',
                     required=True,
                     digits_compute=dp.get_precision('Product Price')),
        'discount':
        fields.float('Discount (%)',
                     digits_compute=dp.get_precision('Discount')),
        'uom_id':
        fields.many2one('product.uom', 'Unit of Measure ', required=True),
        'quantity':
        fields.float('Quantity',
                     required=True,
                     digits_compute=dp.get_precision('Product UoS')),
    }
    _defaults = {
        'quantity': 1,
    }

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

    def product_uom_change(self, cr, uid, ids, product, uom_id, context=None):
        if not uom_id:
            return {'value': {'price_unit': 0.0, 'uom_id': False}}
        return self.on_change_product_id(cr,
                                         uid,
                                         ids,
                                         product,
                                         uom_id=uom_id,
                                         context=context)
Beispiel #20
0
class subscription_subscription(osv.osv):
    _name = "subscription.subscription"
    _description = "Subscription"
    _columns = {
        'name':
        fields.char('Name', required=True),
        'active':
        fields.boolean(
            'Active',
            help=
            "If the active field is set to False, it will allow you to hide the subscription without removing it."
        ),
        'partner_id':
        fields.many2one('res.partner', 'Partner'),
        'notes':
        fields.text('Internal Notes'),
        'user_id':
        fields.many2one('res.users', 'User', required=True),
        'interval_number':
        fields.integer('Interval Qty'),
        'interval_type':
        fields.selection([('days', 'Days'), ('weeks', 'Weeks'),
                          ('months', 'Months')], 'Interval Unit'),
        'exec_init':
        fields.integer('Number of documents'),
        'date_init':
        fields.datetime('First Date'),
        'state':
        fields.selection([('draft', 'Draft'), ('running', 'Running'),
                          ('done', 'Done')],
                         'Status',
                         copy=False),
        'doc_source':
        fields.reference(
            'Source Document',
            required=True,
            selection=_get_document_types,
            size=128,
            help=
            "User can choose the source document on which he wants to create documents"
        ),
        'doc_lines':
        fields.one2many('subscription.subscription.history',
                        'subscription_id',
                        'Documents created',
                        readonly=True),
        'cron_id':
        fields.many2one('ir.cron',
                        'Cron Job',
                        help="Scheduler which runs on subscription",
                        states={
                            'running': [('readonly', True)],
                            'done': [('readonly', True)]
                        }),
        'note':
        fields.text('Notes', help="Description or Summary of Subscription"),
    }
    _defaults = {
        'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
        'user_id': lambda obj, cr, uid, context: uid,
        'active': lambda *a: True,
        'interval_number': lambda *a: 1,
        'interval_type': lambda *a: 'months',
        'doc_source': lambda *a: False,
        'state': lambda *a: 'draft'
    }

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

    def set_process(self, cr, uid, ids, context=None):
        for row in self.read(cr, uid, ids, context=context):
            mapping = {
                'name': 'name',
                'interval_number': 'interval_number',
                'interval_type': 'interval_type',
                'exec_init': 'numbercall',
                'date_init': 'nextcall'
            }
            res = {
                'model': 'subscription.subscription',
                'args': repr([[row['id']]]),
                'function': 'model_copy',
                'priority': 6,
                'user_id': row['user_id'] and row['user_id'][0]
            }
            for key, value in mapping.items():
                res[value] = row[key]
            id = self.pool.get('ir.cron').create(cr, uid, res)
            self.write(cr, uid, [row['id']], {
                'cron_id': id,
                'state': 'running'
            })
        return True

    def model_copy(self, cr, uid, ids, context=None):
        for row in self.read(cr, uid, ids, context=context):
            if not row.get('cron_id', False):
                continue
            cron_ids = [row['cron_id'][0]]
            remaining = self.pool.get('ir.cron').read(
                cr, uid, cron_ids, ['numbercall'])[0]['numbercall']
            try:
                (model_name, id) = row['doc_source'].split(',')
                id = int(id)
                model = self.pool[model_name]
            except:
                raise UserError(
                    _('Please provide another source document.\nThis one does not exist!'
                      ))

            default = {'state': 'draft'}
            doc_obj = self.pool.get('subscription.document')
            document_ids = doc_obj.search(cr, uid,
                                          [('model.model', '=', model_name)])
            doc = doc_obj.browse(cr, uid, document_ids)[0]
            for f in doc.field_ids:
                if f.value == 'date':
                    value = time.strftime('%Y-%m-%d')
                else:
                    value = False
                default[f.field.name] = value

            state = 'running'

            # if there was only one remaining document to generate
            # the subscription is over and we mark it as being done
            if remaining == 1:
                state = 'done'
            id = self.pool[model_name].copy(cr, uid, id, default, context)
            self.pool.get('subscription.subscription.history').create(
                cr, uid, {
                    'subscription_id': row['id'],
                    'date': time.strftime('%Y-%m-%d %H:%M:%S'),
                    'document_id': model_name + ',' + str(id)
                })
            self.write(cr, uid, [row['id']], {'state': state})
        return True

    def unlink(self, cr, uid, ids, context=None):
        for record in self.browse(cr, uid, ids, context or {}):
            if record.state == "running":
                raise UserError(_('You cannot delete an active subscription!'))
        return super(subscription_subscription,
                     self).unlink(cr, uid, ids, context)

    def set_done(self, cr, uid, ids, context=None):
        res = self.read(cr, uid, ids, ['cron_id'])
        ids2 = [x['cron_id'][0] for x in res if x['id']]
        self.pool.get('ir.cron').write(cr, uid, ids2, {'active': False})
        self.write(cr, uid, ids, {'state': 'done'})
        return True

    def set_draft(self, cr, uid, ids, context=None):
        self.write(cr, uid, ids, {'state': 'draft'})
        return True
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
Beispiel #22
0
class hr_evaluation(osv.Model):
    _name = "hr_evaluation.evaluation"
    _inherit = "mail.thread"
    _description = "Employee Appraisal"
    _columns = {
        'date':
        fields.date("Appraisal Deadline", required=True, select=True),
        'employee_id':
        fields.many2one('hr.employee', "Employee", required=True),
        'note_summary':
        fields.text('Appraisal Summary'),
        'note_action':
        fields.text(
            'Action Plan',
            help=
            "If the evaluation does not meet the expectations, you can propose an action plan"
        ),
        'rating':
        fields.selection(
            [
                ('0', 'Significantly below expectations'),
                ('1', 'Do not meet expectations'),
                ('2', 'Meet expectations'),
                ('3', 'Exceeds expectations'),
                ('4', 'Significantly exceeds expectations'),
            ],
            "Appreciation",
            help=
            "This is the appreciation on which the evaluation is summarized."),
        'survey_request_ids':
        fields.one2many('hr.evaluation.interview', 'evaluation_id',
                        'Appraisal Forms'),
        'plan_id':
        fields.many2one('hr_evaluation.plan', 'Plan', required=True),
        'state':
        fields.selection([
            ('draft', 'New'),
            ('cancel', 'Cancelled'),
            ('wait', 'Plan In Progress'),
            ('progress', 'Waiting Appreciation'),
            ('done', 'Done'),
        ],
                         'Status',
                         required=True,
                         readonly=True,
                         copy=False),
        'date_close':
        fields.date('Ending Date', select=True),
    }
    _defaults = {
        'date':
        lambda *a: (parser.parse(datetime.now().strftime('%Y-%m-%d')) +
                    relativedelta(months=+1)).strftime('%Y-%m-%d'),
        'state':
        lambda *a: 'draft',
    }

    def name_get(self, cr, uid, ids, context=None):
        if not ids:
            return []
        reads = self.browse(cr, uid, ids, context=context)
        res = []
        for record in reads:
            name = record.plan_id.name
            employee = record.employee_id.name_related
            res.append((record['id'], name + ' / ' + employee))
        return res

    def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
        vals = {}
        vals['plan_id'] = False
        if employee_id:
            employee_obj = self.pool.get('hr.employee')
            for employee in employee_obj.browse(cr,
                                                uid, [employee_id],
                                                context=context):
                if employee and employee.evaluation_plan_id and employee.evaluation_plan_id.id:
                    vals.update({'plan_id': employee.evaluation_plan_id.id})
        return {'value': vals}

    def button_plan_in_progress(self, cr, uid, ids, context=None):
        hr_eval_inter_obj = self.pool.get('hr.evaluation.interview')
        if context is None:
            context = {}
        for evaluation in self.browse(cr, uid, ids, context=context):
            wait = False
            for phase in evaluation.plan_id.phase_ids:
                children = []
                if phase.action == "bottom-up":
                    children = evaluation.employee_id.child_ids
                elif phase.action in ("top-down", "final"):
                    if evaluation.employee_id.parent_id:
                        children = [evaluation.employee_id.parent_id]
                elif phase.action == "self":
                    children = [evaluation.employee_id]
                for child in children:

                    int_id = hr_eval_inter_obj.create(
                        cr,
                        uid, {
                            'evaluation_id':
                            evaluation.id,
                            'phase_id':
                            phase.id,
                            'deadline':
                            (parser.parse(datetime.now().strftime('%Y-%m-%d'))
                             + relativedelta(months=+1)).strftime('%Y-%m-%d'),
                            'user_id':
                            child.user_id.id,
                        },
                        context=context)
                    if phase.wait:
                        wait = True
                    if not wait:
                        hr_eval_inter_obj.survey_req_waiting_answer(
                            cr, uid, [int_id], context=context)

                    if (not wait) and phase.mail_feature:
                        body = phase.mail_body % {
                            'employee_name': child.name,
                            'user_signature': child.user_id.signature,
                            'eval_name': phase.survey_id.title,
                            'date': time.strftime('%Y-%m-%d'),
                            'time': time
                        }
                        sub = phase.email_subject
                        if child.work_email:
                            vals = {
                                'state': 'outgoing',
                                'subject': sub,
                                'body_html': '<pre>%s</pre>' % body,
                                'email_to': child.work_email,
                                'email_from': evaluation.employee_id.work_email
                            }
                            self.pool.get('mail.mail').create(cr,
                                                              uid,
                                                              vals,
                                                              context=context)

        self.write(cr, uid, ids, {'state': 'wait'}, context=context)
        return True

    def button_final_validation(self, cr, uid, ids, context=None):
        request_obj = self.pool.get('hr.evaluation.interview')
        self.write(cr, uid, ids, {'state': 'progress'}, context=context)
        for evaluation in self.browse(cr, uid, ids, context=context):
            if evaluation.employee_id and evaluation.employee_id.parent_id and evaluation.employee_id.parent_id.user_id:
                self.message_subscribe_users(
                    cr,
                    uid, [evaluation.id],
                    user_ids=[evaluation.employee_id.parent_id.user_id.id],
                    context=context)
            if len(evaluation.survey_request_ids) != len(
                    request_obj.search(cr,
                                       uid,
                                       [('evaluation_id', '=', evaluation.id),
                                        ('state', 'in', ['done', 'cancel'])],
                                       context=context)):
                raise osv.except_osv(
                    _('Warning!'),
                    _("You cannot change state, because some appraisal forms have not been completed."
                      ))
        return True

    def button_done(self, cr, uid, ids, context=None):
        self.write(cr,
                   uid,
                   ids, {
                       'state': 'done',
                       'date_close': time.strftime('%Y-%m-%d')
                   },
                   context=context)
        return True

    def button_cancel(self, cr, uid, ids, context=None):
        interview_obj = self.pool.get('hr.evaluation.interview')
        evaluation = self.browse(cr, uid, ids[0], context)
        interview_obj.survey_req_cancel(
            cr, uid, [r.id for r in evaluation.survey_request_ids])
        self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
        return True

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

    def write(self, cr, uid, ids, vals, context=None):
        if vals.get('employee_id'):
            employee_id = self.pool.get('hr.employee').browse(
                cr, uid, vals.get('employee_id'), context=context)
            if employee_id.parent_id and employee_id.parent_id.user_id:
                vals['message_follower_ids'] = [
                    (4, employee_id.parent_id.user_id.partner_id.id)
                ]
        if 'date' in vals:
            new_vals = {'deadline': vals.get('date')}
            obj_hr_eval_iterview = self.pool.get('hr.evaluation.interview')
            for evaluation in self.browse(cr, uid, ids, context=context):
                for survey_req in evaluation.survey_request_ids:
                    obj_hr_eval_iterview.write(cr,
                                               uid, [survey_req.id],
                                               new_vals,
                                               context=context)
        return super(hr_evaluation, self).write(cr,
                                                uid,
                                                ids,
                                                vals,
                                                context=context)
Beispiel #23
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
Beispiel #24
0
class hr_expense_expense(osv.osv):
    def _amount(self, cr, uid, ids, field_name, arg, context=None):
        res = {}
        for expense in self.browse(cr, uid, ids, context=context):
            total = 0.0
            for line in expense.line_ids:
                total += line.unit_amount * line.unit_quantity
            res[expense.id] = total
        return res

    def _get_expense_from_line(self, cr, uid, ids, context=None):
        return [
            line.expense_id.id for line in self.pool.get(
                'hr.expense8.line').browse(cr, uid, ids, context=context)
        ]

    def _get_currency(self, cr, uid, context=None):
        user = self.pool.get('res.users').browse(cr,
                                                 uid, [uid],
                                                 context=context)[0]
        return user.company_id.currency_id.id

    _name = "hr.expense8.expense"
    _inherit = ['mail.thread']
    _description = "Expense"
    _order = "id desc"
    _track = {
        'state': {
            'hr_expense.mt_expense_approved':
            lambda self, cr, uid, obj, ctx=None: obj.state == 'accepted',
            'hr_expense.mt_expense_refused':
            lambda self, cr, uid, obj, ctx=None: obj.state == 'cancelled',
            'hr_expense.mt_expense_confirmed':
            lambda self, cr, uid, obj, ctx=None: obj.state == 'confirm',
        },
    }

    _columns = {
        'name':
        fields.char('Description',
                    required=True,
                    readonly=True,
                    states={
                        'draft': [('readonly', False)],
                        'confirm': [('readonly', False)]
                    }),
        'id':
        fields.integer('Sheet ID', readonly=True),
        'date':
        fields.date('Date',
                    select=True,
                    readonly=True,
                    states={
                        'draft': [('readonly', False)],
                        'confirm': [('readonly', False)]
                    }),
        'journal_id':
        fields.many2one('account.journal',
                        'Force Journal',
                        help="The journal used when the expense is done."),
        'employee_id':
        fields.many2one('hr.employee',
                        "Employee",
                        required=True,
                        readonly=True,
                        states={
                            'draft': [('readonly', False)],
                            'confirm': [('readonly', False)]
                        }),
        'user_id':
        fields.many2one('res.users', 'User', required=True),
        'date_confirm':
        fields.date(
            'Confirmation Date',
            select=True,
            copy=False,
            help=
            "Date of the confirmation of the sheet expense. It's filled when the button Confirm is pressed."
        ),
        'date_valid':
        fields.date(
            'Validation Date',
            select=True,
            copy=False,
            help=
            "Date of the acceptation of the sheet expense. It's filled when the button Accept is pressed."
        ),
        'user_valid':
        fields.many2one('res.users',
                        'Validation By',
                        readonly=True,
                        copy=False,
                        states={
                            'draft': [('readonly', False)],
                            'confirm': [('readonly', False)]
                        }),
        'account_move_id':
        fields.many2one('account.move', 'Ledger Posting', copy=False),
        'line_ids':
        fields.one2many('hr.expense8.line',
                        'expense_id',
                        'Expense Lines',
                        copy=True,
                        readonly=True,
                        states={'draft': [('readonly', False)]}),
        'note':
        fields.text('Note'),
        'amount':
        fields.function(_amount,
                        string='Total Amount',
                        digits_compute=dp.get_precision('Account'),
                        store={
                            'hr.expense8.line':
                            (_get_expense_from_line,
                             ['unit_amount', 'unit_quantity'], 10)
                        }),
        'currency_id':
        fields.many2one('res.currency',
                        'Currency',
                        required=True,
                        readonly=True,
                        states={
                            'draft': [('readonly', False)],
                            'confirm': [('readonly', False)]
                        }),
        'department_id':
        fields.many2one('hr.department',
                        'Department',
                        readonly=True,
                        states={
                            'draft': [('readonly', False)],
                            'confirm': [('readonly', False)]
                        }),
        'company_id':
        fields.many2one('res.company', 'Company', required=True),
        'state':
        fields.selection(
            [
                ('draft', 'New'),
                ('cancelled', 'Refused'),
                ('confirm', 'Waiting Approval'),
                ('accepted', 'Approved'),
                ('done', 'Waiting Payment'),
                ('paid', 'Paid'),
            ],
            'Status',
            readonly=True,
            track_visibility='onchange',
            copy=False,
            help=
            'When the expense request is created the status is \'Draft\'.\n It is confirmed by the user and request is sent to admin, the status is \'Waiting Confirmation\'.\
            \nIf the admin accepts it, the status is \'Accepted\'.\n If the accounting entries are made for the expense request, the status is \'Waiting Payment\'.'
        ),
    }
    _defaults = {
        'company_id':
        lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(
            cr, uid, 'hr.employee', context=c),
        'date':
        fields.date.context_today,
        'state':
        'draft',
        'employee_id':
        _employee_get,
        'user_id':
        lambda cr, uid, id, c={}: id,
        'currency_id':
        _get_currency,
    }

    def unlink(self, cr, uid, ids, context=None):
        for rec in self.browse(cr, uid, ids, context=context):
            if rec.state != 'draft':
                raise osv.except_osv(_('Warning!'),
                                     _('You can only delete draft expenses!'))
        return super(hr_expense_expense, self).unlink(cr, uid, ids, context)

    def onchange_currency_id(self,
                             cr,
                             uid,
                             ids,
                             currency_id=False,
                             company_id=False,
                             context=None):
        res = {'value': {'journal_id': False}}
        journal_ids = self.pool.get('account.journal').search(
            cr,
            uid, [('type', '=', 'purchase'), ('company_id', '=', company_id)],
            context=context)
        if journal_ids:
            res['value']['journal_id'] = journal_ids[0]
        return res

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

    def expense_confirm(self, cr, uid, ids, context=None):
        for expense in self.browse(cr, uid, ids):
            if expense.employee_id and expense.employee_id.parent_id.user_id:
                self.message_subscribe_users(
                    cr,
                    uid, [expense.id],
                    user_ids=[expense.employee_id.parent_id.user_id.id])
        return self.write(cr,
                          uid,
                          ids, {
                              'state': 'confirm',
                              'date_confirm': time.strftime('%Y-%m-%d')
                          },
                          context=context)

    def expense_accept(self, cr, uid, ids, context=None):
        return self.write(cr,
                          uid,
                          ids, {
                              'state': 'accepted',
                              'date_valid': time.strftime('%Y-%m-%d'),
                              'user_valid': uid
                          },
                          context=context)

    def expense_canceled(self, cr, uid, ids, context=None):
        return self.write(cr,
                          uid,
                          ids, {'state': 'cancelled'},
                          context=context)

    def account_move_get(self, cr, uid, expense_id, context=None):
        '''
        This method prepare the creation of the account move related to the given expense.

        :param expense_id: Id of expense for which we are creating account_move.
        :return: mapping between fieldname and value of account move to create
        :rtype: dict
        '''
        journal_obj = self.pool.get('account.journal')
        expense = self.browse(cr, uid, expense_id, context=context)
        company_id = expense.company_id.id
        date = expense.date_confirm
        ref = expense.name
        journal_id = False
        if expense.journal_id:
            journal_id = expense.journal_id.id
        else:
            journal_id = journal_obj.search(cr, uid,
                                            [('type', '=', 'purchase'),
                                             ('company_id', '=', company_id)])
            if not journal_id:
                raise osv.except_osv(
                    _('Error!'),
                    _("No expense journal found. Please make sure you have a journal with type 'purchase' configured."
                      ))
            journal_id = journal_id[0]
        return self.pool.get('account.move').account_move_prepare(
            cr,
            uid,
            journal_id,
            date=date,
            ref=ref,
            company_id=company_id,
            context=context)

    def line_get_convert(self, cr, uid, x, part, date, context=None):
        partner_id = self.pool.get('res.partner')._find_accounting_partner(
            part).id
        return {
            'date_maturity':
            x.get('date_maturity', False),
            'partner_id':
            partner_id,
            'name':
            x['name'][:64],
            'date':
            date,
            'debit':
            x['price'] > 0 and x['price'],
            'credit':
            x['price'] < 0 and -x['price'],
            'account_id':
            x['account_id'],
            'analytic_lines':
            x.get('analytic_lines', False),
            'amount_currency':
            x['price'] > 0 and abs(x.get('amount_currency', False))
            or -abs(x.get('amount_currency', False)),
            'currency_id':
            x.get('currency_id', False),
            'tax_code_id':
            x.get('tax_code_id', False),
            'tax_amount':
            x.get('tax_amount', False),
            'ref':
            x.get('ref', False),
            'quantity':
            x.get('quantity', 1.00),
            'product_id':
            x.get('product_id', False),
            'product_uom_id':
            x.get('uos_id', False),
            'analytic_account_id':
            x.get('account_analytic_id', False),
        }

    def compute_expense_totals(self,
                               cr,
                               uid,
                               exp,
                               company_currency,
                               ref,
                               account_move_lines,
                               context=None):
        '''
        internal method used for computation of total amount of an expense in the company currency and
        in the expense currency, given the account_move_lines that will be created. It also do some small
        transformations at these account_move_lines (for multi-currency purposes)
        
        :param account_move_lines: list of dict
        :rtype: tuple of 3 elements (a, b ,c)
            a: total in company currency
            b: total in hr.expense currency
            c: account_move_lines potentially modified
        '''
        cur_obj = self.pool.get('res.currency')
        context = dict(context or {},
                       date=exp.date_confirm or time.strftime('%Y-%m-%d'))
        total = 0.0
        total_currency = 0.0
        for i in account_move_lines:
            if exp.currency_id.id != company_currency:
                i['currency_id'] = exp.currency_id.id
                i['amount_currency'] = i['price']
                i['price'] = cur_obj.compute(cr,
                                             uid,
                                             exp.currency_id.id,
                                             company_currency,
                                             i['price'],
                                             context=context)
            else:
                i['amount_currency'] = False
                i['currency_id'] = False
            total -= i['price']
            total_currency -= i['amount_currency'] or i['price']
        return total, total_currency, account_move_lines

    def action_move_create(self, cr, uid, ids, context=None):
        '''
        main function that is called when trying to create the accounting entries related to an expense
        '''
        move_obj = self.pool.get('account.move')
        for exp in self.browse(cr, uid, ids, context=context):
            if not exp.employee_id.address_home_id:
                raise osv.except_osv(
                    _('Error!'), _('The employee must have a home address.'))
            #zhangzs
            # if not exp.employee_id.address_home_id.property_account_payable.id:
            #     raise osv.except_osv(_('Error!'), _('The employee must have a payable account set on his home address.'))

            company_currency = exp.company_id.currency_id.id
            diff_currency_p = exp.currency_id.id <> company_currency

            #create the move that will contain the accounting entries
            move_id = move_obj.create(cr,
                                      uid,
                                      self.account_move_get(cr,
                                                            uid,
                                                            exp.id,
                                                            context=context),
                                      context=context)

            #one account.move.line per expense line (+taxes..)
            eml = self.move_line_get(cr, uid, exp.id, context=context)

            #create one more move line, a counterline for the total on payable account
            total, total_currency, eml = self.compute_expense_totals(
                cr, uid, exp, company_currency, exp.name, eml, context=context)
            acc = exp.employee_id.address_home_id.property_account_payable.id
            eml.append({
                'type':
                'dest',
                'name':
                '/',
                'price':
                total,
                'account_id':
                acc,
                'date_maturity':
                exp.date_confirm,
                'amount_currency':
                diff_currency_p and total_currency or False,
                'currency_id':
                diff_currency_p and exp.currency_id.id or False,
                'ref':
                exp.name
            })

            #convert eml into an osv-valid format
            lines = map(
                lambda x:
                (0, 0,
                 self.line_get_convert(cr,
                                       uid,
                                       x,
                                       exp.employee_id.address_home_id,
                                       exp.date_confirm,
                                       context=context)), eml)
            journal_id = move_obj.browse(cr, uid, move_id, context).journal_id
            # post the journal entry if 'Skip 'Draft' State for Manual Entries' is checked
            if journal_id.entry_posted:
                move_obj.button_validate(cr, uid, [move_id], context)
            move_obj.write(cr,
                           uid, [move_id], {'line_id': lines},
                           context=context)
            self.write(cr,
                       uid,
                       ids, {
                           'account_move_id': move_id,
                           'state': 'done'
                       },
                       context=context)
        return True

    def move_line_get(self, cr, uid, expense_id, context=None):
        res = []
        tax_obj = self.pool.get('account.tax')
        cur_obj = self.pool.get('res.currency')
        if context is None:
            context = {}
        exp = self.browse(cr, uid, expense_id, context=context)
        company_currency = exp.company_id.currency_id.id

        for line in exp.line_ids:
            mres = self.move_line_get_item(cr, uid, line, context)
            if not mres:
                continue
            res.append(mres)

            #Calculate tax according to default tax on product
            taxes = []
            #Taken from product_id_onchange in account.invoice
            if line.product_id:
                fposition_id = False
                fpos_obj = self.pool.get('account.fiscal.position')
                fpos = fposition_id and fpos_obj.browse(
                    cr, uid, fposition_id, context=context) or False
                product = line.product_id
                taxes = product.supplier_taxes_id
                #If taxes are not related to the product, maybe they are in the account
                if not taxes:
                    a = product.property_account_expense.id  #Why is not there a check here?
                    if not a:
                        a = product.categ_id.property_account_expense_categ.id
                    a = fpos_obj.map_account(cr, uid, fpos, a)
                    taxes = a and self.pool.get('account.account').browse(
                        cr, uid, a, context=context).tax_ids or False
            if not taxes:
                continue
            tax_l = []
            base_tax_amount = line.total_amount
            #Calculating tax on the line and creating move?
            for tax in tax_obj.compute_all(cr, uid, taxes, line.unit_amount,
                                           line.unit_quantity, line.product_id,
                                           exp.user_id.partner_id)['taxes']:
                tax_code_id = tax['base_code_id']
                if not tax_code_id:
                    continue
                res[-1]['tax_code_id'] = tax_code_id
                ##
                is_price_include = tax_obj.read(cr, uid, tax['id'],
                                                ['price_include'],
                                                context)['price_include']
                if is_price_include:
                    ## We need to deduce the price for the tax
                    res[-1]['price'] = res[-1]['price'] - tax['amount']
                    # tax amount countains base amount without the tax
                    base_tax_amount = (base_tax_amount -
                                       tax['amount']) * tax['base_sign']
                else:
                    base_tax_amount = base_tax_amount * tax['base_sign']

                assoc_tax = {
                    'type': 'tax',
                    'name': tax['name'],
                    'price_unit': tax['price_unit'],
                    'quantity': 1,
                    'price': tax['amount'] or 0.0,
                    'account_id': tax['account_collected_id']
                    or mres['account_id'],
                    'tax_code_id': tax['tax_code_id'],
                    'tax_amount': tax['amount'] * tax['base_sign'],
                }
                tax_l.append(assoc_tax)

            res[-1]['tax_amount'] = cur_obj.compute(
                cr,
                uid,
                exp.currency_id.id,
                company_currency,
                base_tax_amount,
                context={'date': exp.date_confirm})
            res += tax_l
        return res

    def move_line_get_item(self, cr, uid, line, context=None):
        company = line.expense_id.company_id
        property_obj = self.pool.get('ir.property')
        if line.product_id:
            acc = line.product_id.property_account_expense
            if not acc:
                acc = line.product_id.categ_id.property_account_expense_categ
            if not acc:
                raise osv.except_osv(
                    _('Error!'),
                    _('No purchase account found for the product %s (or for his category), please configure one.'
                      ) % (line.product_id.name))
        else:
            acc = property_obj.get(cr,
                                   uid,
                                   'property_account_expense_categ',
                                   'product.category',
                                   context={'force_company': company.id})
            if not acc:
                raise osv.except_osv(
                    _('Error!'),
                    _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'
                      ))
        return {
            'type': 'src',
            'name': line.name.split('\n')[0][:64],
            'price_unit': line.unit_amount,
            'quantity': line.unit_quantity,
            'price': line.total_amount,
            'account_id': acc.id,
            'product_id': line.product_id.id,
            'uos_id': line.uom_id.id,
            'account_analytic_id': line.analytic_account.id,
        }

    def action_view_move(self, cr, uid, ids, context=None):
        '''
        This function returns an action that display existing account.move of given expense ids.
        '''
        assert len(
            ids
        ) == 1, 'This option should only be used for a single id at a time'
        expense = self.browse(cr, uid, ids[0], context=context)
        assert expense.account_move_id
        try:
            dummy, view_id = self.pool.get(
                'ir.model.data').get_object_reference(cr, uid, 'account',
                                                      'view_move_form')
        except ValueError, e:
            view_id = False
        result = {
            'name': _('Expense Account Move'),
            'view_type': 'form',
            'view_mode': 'form',
            'view_id': view_id,
            'res_model': 'account.move',
            'type': 'ir.actions.act_window',
            'nodestroy': True,
            'target': 'current',
            'res_id': expense.account_move_id.id,
        }
        return result
Beispiel #25
0
class PaymentTransaction(osv.Model):
    """ Transaction Model. Each specific acquirer can extend the model by adding
    its own fields.

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

     - ``<name>_create``: method receiving values used when creating a new
       transaction and that returns a dictionary that will update those values.
       This method can be used to tweak some transaction values.

    Methods defined for convention, depending on your controllers:

     - ``<name>_form_feedback(self, cr, uid, data, context=None)``: method that
       handles the data coming from the acquirer after the transaction. It will
       generally receives data posted by the acquirer after the transaction.
    """
    _name = 'payment.transaction'
    _description = 'Payment Transaction'
    _order = 'id desc'
    _rec_name = 'reference'

    def _lang_get(self, cr, uid, context=None):
        lang_ids = self.pool['res.lang'].search(cr, uid, [], context=context)
        languages = self.pool['res.lang'].browse(cr, uid, lang_ids, context=context)
        return [(language.code, language.name) for language in languages]

    def _default_partner_country_id(self, cr, uid, context=None):
        comp = self.pool['res.company'].browse(cr, uid, context.get('company_id', 1), context=context)
        return comp.country_id.id

    _columns = {
        'create_date': fields.datetime('Creation Date', readonly=True),
        'date_validate': fields.datetime('Validation Date'),
        'acquirer_id': fields.many2one(
            'payment.acquirer', 'Acquirer',
            required=True,
        ),
        'type': fields.selection(
            [('server2server', 'Server To Server'), ('form', 'Form'), ('form_save', 'Form with credentials storage')],
            string='Type', required=True),
        'state': fields.selection(
            [('draft', 'Draft'), ('pending', 'Pending'),
             ('done', 'Done'), ('error', 'Error'),
             ('cancel', 'Canceled')
             ], 'Status', required=True,
            track_visibility='onchange', copy=False),
        'state_message': fields.text('Message',
                                     help='Field used to store error and/or validation messages for information'),
        # payment
        'amount': fields.float('Amount', required=True,
                               digits=(16, 2),
                               track_visibility='always',
                               help='Amount'),
        'fees': fields.float('Fees',
                             digits=(16, 2),
                             track_visibility='always',
                             help='Fees amount; set by the system because depends on the acquirer'),
        'currency_id': fields.many2one('res.currency', 'Currency', required=True),
        'reference': fields.char('Reference', required=True, help='Internal reference of the TX'),
        'acquirer_reference': fields.char('Acquirer Reference',
                                          help='Reference of the TX as stored in the acquirer database'),
        # duplicate partner / transaction data to store the values at transaction time
        'partner_id': fields.many2one('res.partner', 'Partner', track_visibility='onchange',),
        'partner_name': fields.char('Partner Name'),
        'partner_lang': fields.selection(_lang_get, 'Language'),
        'partner_email': fields.char('Email'),
        'partner_zip': fields.char('Zip'),
        'partner_address': fields.char('Address'),
        'partner_city': fields.char('City'),
        'partner_country_id': fields.many2one('res.country', 'Country', required=True),
        'partner_phone': fields.char('Phone'),
        'html_3ds': fields.char('3D Secure HTML'),

        'callback_eval': fields.char('S2S Callback', help="""\
            Will be safe_eval with `self` being the current transaction. i.e.:
                self.env['my.model'].payment_validated(self)""", oldname="s2s_cb_eval"),
        'payment_method_id': fields.many2one('payment.method', 'Payment Method', domain="[('acquirer_id', '=', acquirer_id)]"),
    }

    def _check_reference(self, cr, uid, ids, context=None):
        transaction = self.browse(cr, uid, ids[0], context=context)
        if transaction.state not in ['cancel', 'error']:
            if self.search(cr, uid, [('reference', '=', transaction.reference), ('id', '!=', transaction.id)], context=context, count=True):
                return False
        return True

    _constraints = [
        (_check_reference, 'The payment transaction reference must be unique!', ['reference', 'state']),
    ]

    _defaults = {
        'type': 'form',
        'state': 'draft',
        'partner_lang': 'en_US',
        'partner_country_id': _default_partner_country_id,
        'reference': lambda s, c, u, ctx=None: s.pool['ir.sequence'].next_by_code(c, u, 'payment.transaction', context=ctx),
    }

    def create(self, cr, uid, values, context=None):
        Acquirer = self.pool['payment.acquirer']

        if values.get('partner_id'):  # @TDENOTE: not sure
            values.update(self.on_change_partner_id(cr, uid, None, values.get('partner_id'), context=context)['value'])

        # call custom create method if defined (i.e. ogone_create for ogone)
        if values.get('acquirer_id'):
            acquirer = self.pool['payment.acquirer'].browse(cr, uid, values.get('acquirer_id'), context=context)

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

            # custom create
            custom_method_name = '%s_create' % acquirer.provider
            if hasattr(self, custom_method_name):
                values.update(getattr(self, custom_method_name)(cr, uid, values, context=context))

        # Default value of reference is
        tx_id = super(PaymentTransaction, self).create(cr, uid, values, context=context)
        if not values.get('reference'):
            self.write(cr, uid, [tx_id], {'reference': str(tx_id)}, context=context)
        return tx_id

    def write(self, cr, uid, ids, values, context=None):
        Acquirer = self.pool['payment.acquirer']
        if ('acquirer_id' in values or 'amount' in values) and 'fees' not in values:
            # The acquirer or the amount has changed, and the fees are not explicitely forced. Fees must be recomputed.
            if isinstance(ids, (int, long)):
                ids = [ids]
            for txn_id in ids:
                vals = dict(values)
                vals['fees'] = 0.0
                transaction = self.browse(cr, uid, txn_id, context=context)
                if 'acquirer_id' in values:
                    acquirer = Acquirer.browse(cr, uid, values['acquirer_id'], context=context) if values['acquirer_id'] else None
                else:
                    acquirer = transaction.acquirer_id
                if acquirer:
                    custom_method_name = '%s_compute_fees' % acquirer.provider
                    if hasattr(Acquirer, custom_method_name):
                        amount = (values['amount'] if 'amount' in values else transaction.amount) or 0.0
                        currency_id = values.get('currency_id') or transaction.currency_id.id
                        country_id = values.get('partner_country_id') or transaction.partner_country_id.id
                        fees = getattr(Acquirer, custom_method_name)(cr, uid, acquirer.id, amount, currency_id, country_id, context=None)
                        vals['fees'] = float_round(fees, 2)
                res = super(PaymentTransaction, self).write(cr, uid, txn_id, vals, context=context)
            return res
        return super(PaymentTransaction, self).write(cr, uid, ids, values, context=context)

    def on_change_partner_id(self, cr, uid, ids, partner_id, context=None):
        partner = None
        if partner_id:
            partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
            return {'value': {
                'partner_name': partner and partner.name or False,
                'partner_lang': partner and partner.lang or 'en_US',
                'partner_email': partner and partner.email or False,
                'partner_zip': partner and partner.zip or False,
                'partner_address': _partner_format_address(partner and partner.street or '', partner and partner.street2 or ''),
                'partner_city': partner and partner.city or False,
                'partner_country_id': partner and partner.country_id.id or self._default_partner_country_id(cr, uid, context=context),
                'partner_phone': partner and partner.phone or False,
            }}
        return {}

    def get_next_reference(self, cr, uid, reference, context=None):
        ref_suffix = 1
        init_ref = reference
        while self.pool['payment.transaction'].search_count(cr, uid, [('reference', '=', reference)], context=context):
            reference = init_ref + '-' + str(ref_suffix)
            ref_suffix += 1
        return reference

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

    def render(self, cr, uid, id, context=None):
        tx = self.browse(cr, uid, id, context=context)
        values = {
            'reference': tx.reference,
            'amount': tx.amount,
            'currency_id': tx.currency_id.id,
            'currency': tx.currency_id,
            'partner': tx.partner_id,
            'partner_name': tx.partner_name,
            'partner_lang': tx.partner_lang,
            'partner_email': tx.partner_email,
            'partner_zip': tx.partner_zip,
            'partner_address': tx.partner_address,
            'partner_city': tx.partner_city,
            'partner_country_id': tx.partner_country_id.id,
            'partner_country': tx.partner_country_id,
            'partner_phone': tx.partner_phone,
            'partner_state': None,
        }
        return tx.acquirer_id.render(None, None, None, values=values)

    def form_feedback(self, cr, uid, data, acquirer_name, context=None):
        invalid_parameters, tx = None, None

        tx_find_method_name = '_%s_form_get_tx_from_data' % acquirer_name
        if hasattr(self, tx_find_method_name):
            tx = getattr(self, tx_find_method_name)(cr, uid, data, context=context)

        invalid_param_method_name = '_%s_form_get_invalid_parameters' % acquirer_name
        if hasattr(self, invalid_param_method_name):
            invalid_parameters = getattr(self, invalid_param_method_name)(cr, uid, tx, data, context=context)

        if invalid_parameters:
            _error_message = '%s: incorrect tx data:\n' % (acquirer_name)
            for item in invalid_parameters:
                _error_message += '\t%s: received %s instead of %s\n' % (item[0], item[1], item[2])
            _logger.error(_error_message)
            return False

        feedback_method_name = '_%s_form_validate' % acquirer_name
        if hasattr(self, feedback_method_name):
            return getattr(self, feedback_method_name)(cr, uid, tx, data, context=context)

        return True

    # --------------------------------------------------
    # SERVER2SERVER RELATED METHODS
    # --------------------------------------------------
    def s2s_create(self, cr, uid, values, cc_values, context=None):
        tx_id, tx_result = self.s2s_send(cr, uid, values, cc_values, context=context)
        self.s2s_feedback(cr, uid, tx_id, tx_result, context=context)
        return tx_id

    def s2s_do_transaction(self, cr, uid, id, context=None, **kwargs):
        tx = self.browse(cr, uid, id, context=context)
        custom_method_name = '%s_s2s_do_transaction' % tx.acquirer_id.provider
        if hasattr(self, custom_method_name):
            return getattr(self, custom_method_name)(cr, uid, id, context=context, **kwargs)

    def s2s_get_tx_status(self, cr, uid, tx_id, context=None):
        """ Get the tx status. """
        tx = self.browse(cr, uid, tx_id, context=context)

        invalid_param_method_name = '_%s_s2s_get_tx_status' % tx.acquirer_id.provider
        if hasattr(self, invalid_param_method_name):
            return getattr(self, invalid_param_method_name)(cr, uid, tx, context=context)

        return True
Beispiel #26
0
class hr_expense_line(osv.osv):
    _name = "hr.expense8.line"
    _description = "Expense Line"

    def _amount(self, cr, uid, ids, field_name, arg, context=None):
        if not ids:
            return {}
        cr.execute(
            "SELECT l.id,COALESCE(SUM(l.unit_amount*l.unit_quantity),0) AS amount FROM hr_expense8_line l WHERE id IN %s GROUP BY l.id ",
            (tuple(ids), ))
        res = dict(cr.fetchall())
        return res

    def _get_uom_id(self, cr, uid, context=None):
        result = self.pool.get('ir.model.data').get_object_reference(
            cr, uid, 'product', 'product_uom_unit')
        return result and result[1] or False

    _columns = {
        'name':
        fields.char('Expense Note', required=True),
        'date_value':
        fields.date('Date', required=True),
        'expense_id':
        fields.many2one('hr.expense8.expense',
                        'Expense',
                        ondelete='cascade',
                        select=True),
        'total_amount':
        fields.function(_amount,
                        string='Total',
                        digits_compute=dp.get_precision('Account')),
        'unit_amount':
        fields.float('Unit Price',
                     digits_compute=dp.get_precision('Product Price')),
        'unit_quantity':
        fields.float(
            'Quantities',
            digits_compute=dp.get_precision('Product Unit of Measure')),
        'product_id':
        fields.many2one('product.product',
                        'Product',
                        domain=[('hr_expense_ok', '=', True)]),
        'uom_id':
        fields.many2one('product.uom', 'Unit of Measure', required=True),
        'description':
        fields.text('Description'),
        'analytic_account':
        fields.many2one('account.analytic.account', 'Analytic account'),
        'ref':
        fields.char('Reference'),
        'sequence':
        fields.integer(
            'Sequence',
            select=True,
            help=
            "Gives the sequence order when displaying a list of expense lines."
        ),
    }
    _defaults = {
        'unit_quantity': 1,
        'date_value': lambda *a: time.strftime('%Y-%m-%d'),
        'uom_id': _get_uom_id,
    }
    _order = "sequence, date_value desc"

    def onchange_product_id(self, cr, uid, ids, product_id, context=None):
        res = {}
        if product_id:
            product = self.pool.get('product.product').browse(cr,
                                                              uid,
                                                              product_id,
                                                              context=context)
            res['name'] = product.name
            amount_unit = product.price_get('standard_price')[product.id]
            res['unit_amount'] = amount_unit
            res['uom_id'] = product.uom_id.id
        return {'value': res}

    def onchange_uom(self, cr, uid, ids, product_id, uom_id, context=None):
        res = {'value': {}}
        if not uom_id or not product_id:
            return res
        product = self.pool.get('product.product').browse(cr,
                                                          uid,
                                                          product_id,
                                                          context=context)
        uom = self.pool.get('product.uom').browse(cr,
                                                  uid,
                                                  uom_id,
                                                  context=context)
        if uom.category_id.id != product.uom_id.category_id.id:
            res['warning'] = {
                'title':
                _('Warning'),
                'message':
                _('Selected Unit of Measure does not belong to the same category as the product Unit of Measure'
                  )
            }
            res['value'].update({'uom_id': product.uom_id.id})
        return res
Beispiel #27
0
class ir_actions_report_xml(orm.Model):
    _inherit = 'ir.actions.report.xml'
    _columns = {
        'webkit_header':
        fields.property(type='many2one',
                        relation='ir.header_webkit',
                        string='Webkit Header',
                        help="The header linked to the report",
                        required=True),
        'webkit_debug':
        fields.boolean('Webkit debug',
                       help="Enable the webkit engine debugger"),
        'report_webkit_data':
        fields.text(
            'Webkit Template',
            help=
            "This template will be used if the main report file is not found"),
        'precise_mode':
        fields.boolean(
            'Precise Mode',
            help="This mode allow more precise element position as each object"
            " is printed on a separate HTML but memory and disk usage are wider."
        )
    }

    def _lookup_report(self, cr, name):
        """
        Look up a report definition.
        """
        import operator
        import os
        opj = os.path.join

        # First lookup in the deprecated place, because if the report definition
        # has not been updated, it is more likely the correct definition is there.
        # Only reports with custom parser specified in Python are still there.
        if 'report.' + name in yuancloud.report.interface.report_int._reports:
            new_report = yuancloud.report.interface.report_int._reports[
                'report.' + name]
            if not isinstance(new_report, WebKitParser):
                new_report = None
        else:
            cr.execute(
                "SELECT * FROM ir_act_report_xml WHERE report_name=%s and report_type=%s",
                (name, 'webkit'))
            r = cr.dictfetchone()
            if r:
                if r['parser']:
                    parser = operator.attrgetter(r['parser'])(yuancloud.addons)
                    kwargs = {'parser': parser}
                else:
                    kwargs = {}

                new_report = WebKitParser('report.' + r['report_name'],
                                          r['model'],
                                          opj('addons', r['report_rml']
                                              or '/'),
                                          header=r['header'],
                                          register=False,
                                          **kwargs)
            else:
                new_report = None

        if new_report:
            return new_report
        else:
            return super(ir_actions_report_xml, self)._lookup_report(cr, name)
Beispiel #28
0
class hr_evaluation_plan_phase(osv.Model):
    _name = "hr_evaluation.plan.phase"
    _description = "Appraisal Plan Phase"
    _order = "sequence"
    _columns = {
        'name':
        fields.char("Phase", size=64, required=True),
        'sequence':
        fields.integer("Sequence"),
        'company_id':
        fields.related('plan_id',
                       'company_id',
                       type='many2one',
                       relation='res.company',
                       string='Company',
                       store=True,
                       readonly=True),
        'plan_id':
        fields.many2one('hr_evaluation.plan',
                        'Appraisal Plan',
                        ondelete='cascade'),
        'action':
        fields.selection([('top-down', 'Top-Down Appraisal Requests'),
                          ('bottom-up', 'Bottom-Up Appraisal Requests'),
                          ('self', 'Self Appraisal Requests'),
                          ('final', 'Final Interview')],
                         'Action',
                         required=True),
        'survey_id':
        fields.many2one('survey.survey', 'Appraisal Form', required=True),
        'send_answer_manager':
        fields.boolean('All Answers', help="Send all answers to the manager"),
        'send_answer_employee':
        fields.boolean('All Answers', help="Send all answers to the employee"),
        'send_anonymous_manager':
        fields.boolean('Anonymous Summary',
                       help="Send an anonymous summary to the manager"),
        'send_anonymous_employee':
        fields.boolean('Anonymous Summary',
                       help="Send an anonymous summary to the employee"),
        'wait':
        fields.boolean(
            'Wait Previous Phases',
            help="Check this box if you want to wait that all preceding phases "
            + "are finished before launching this phase."),
        'mail_feature':
        fields.boolean(
            'Send mail for this phase',
            help=
            "Check this box if you want to send mail to employees coming under this phase"
        ),
        'mail_body':
        fields.text('Email'),
        'email_subject':
        fields.text('Subject')
    }
    _defaults = {
        'sequence':
        1,
        'email_subject':
        _('''Regarding '''),
        'mail_body':
        lambda *a: _('''
Date: %(date)s

Dear %(employee_name)s,

I am doing an evaluation regarding %(eval_name)s.

Kindly submit your response.


Thanks,
--
%(user_signature)s

        '''),
    }
Beispiel #29
0
class procurement_order(osv.osv):
    """
    Procurement Orders
    """
    _name = "procurement.order"
    _description = "Procurement"
    _order = 'priority desc, date_planned, id asc'
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _log_create = False
    _columns = {
        'name':
        fields.text('Description', required=True),
        'origin':
        fields.char(
            'Source Document',
            help="Reference of the document that created this Procurement.\n"
            "This is automatically completed by YuanCloud."),
        'company_id':
        fields.many2one('res.company', 'Company', required=True),

        # These two fields are used for shceduling
        'priority':
        fields.selection(PROCUREMENT_PRIORITIES,
                         'Priority',
                         required=True,
                         select=True,
                         track_visibility='onchange'),
        'date_planned':
        fields.datetime('Scheduled Date',
                        required=True,
                        select=True,
                        track_visibility='onchange'),
        'group_id':
        fields.many2one('procurement.group', 'Procurement Group'),
        'rule_id':
        fields.many2one(
            'procurement.rule',
            'Rule',
            track_visibility='onchange',
            help=
            "Chosen rule for the procurement resolution. Usually chosen by the system but can be manually set by the procurement manager to force an unusual behavior."
        ),
        'product_id':
        fields.many2one('product.product',
                        'Product',
                        required=True,
                        states={'confirmed': [('readonly', False)]},
                        readonly=True),
        'product_qty':
        fields.float(
            'Quantity',
            digits_compute=dp.get_precision('Product Unit of Measure'),
            required=True,
            states={'confirmed': [('readonly', False)]},
            readonly=True),
        'product_uom':
        fields.many2one('product.uom',
                        'Product Unit of Measure',
                        required=True,
                        states={'confirmed': [('readonly', False)]},
                        readonly=True),
        'state':
        fields.selection([('cancel', 'Cancelled'), ('confirmed', 'Confirmed'),
                          ('exception', 'Exception'), ('running', 'Running'),
                          ('done', 'Done')],
                         'Status',
                         required=True,
                         track_visibility='onchange',
                         copy=False),
    }

    _defaults = {
        'state':
        'confirmed',
        'priority':
        '1',
        'date_planned':
        lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
        'company_id':
        lambda self, cr, uid, c: self.pool.get('res.company').
        _company_default_get(cr, uid, 'procurement.order', context=c)
    }

    def _needaction_domain_get(self, cr, uid, context=None):
        return [('state', '=', 'exception')]

    def unlink(self, cr, uid, ids, context=None):
        procurements = self.read(cr, uid, ids, ['state'], context=context)
        unlink_ids = []
        for s in procurements:
            if s['state'] == 'cancel':
                unlink_ids.append(s['id'])
            else:
                raise UserError(
                    _('Cannot delete Procurement Order(s) which are in %s state.'
                      ) % s['state'])
        return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)

    def create(self, cr, uid, vals, context=None):
        context = context or {}
        procurement_id = super(procurement_order, self).create(cr,
                                                               uid,
                                                               vals,
                                                               context=context)
        if not context.get('procurement_autorun_defer'):
            self.run(cr, uid, [procurement_id], context=context)
        return procurement_id

    def do_view_procurements(self, cr, uid, ids, context=None):
        '''
        This function returns an action that display existing procurement orders
        of same procurement group of given ids.
        '''
        act_obj = self.pool.get('ir.actions.act_window')
        action_id = self.pool.get('ir.model.data').xmlid_to_res_id(
            cr,
            uid,
            'procurement.do_view_procurements',
            raise_if_not_found=True)
        result = act_obj.read(cr, uid, [action_id], context=context)[0]
        group_ids = set([
            proc.group_id.id
            for proc in self.browse(cr, uid, ids, context=context)
            if proc.group_id
        ])
        result['domain'] = "[('group_id','in',[" + ','.join(
            map(str, list(group_ids))) + "])]"
        return result

    def onchange_product_id(self, cr, uid, ids, product_id, context=None):
        """ Finds UoM of changed product.
        @param product_id: Changed id of product.
        @return: Dictionary of values.
        """
        if product_id:
            w = self.pool.get('product.product').browse(cr,
                                                        uid,
                                                        product_id,
                                                        context=context)
            v = {
                'product_uom': w.uom_id.id,
            }
            return {'value': v}
        return {}

    def get_cancel_ids(self, cr, uid, ids, context=None):
        return [
            proc.id for proc in self.browse(cr, uid, ids, context=context)
            if proc.state != 'done'
        ]

    def cancel(self, cr, uid, ids, context=None):
        #cancel only the procurements that aren't done already
        to_cancel_ids = self.get_cancel_ids(cr, uid, ids, context=context)
        if to_cancel_ids:
            return self.write(cr,
                              uid,
                              to_cancel_ids, {'state': 'cancel'},
                              context=context)

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

    @api.v8
    def run(self, autocommit=False):
        return self._model.run(self._cr,
                               self._uid,
                               self.ids,
                               autocommit=False,
                               context=self._context)

    @api.v7
    def run(self, cr, uid, ids, autocommit=False, context=None):
        for procurement_id in ids:
            #we intentionnaly do the browse under the for loop to avoid caching all ids which would be resource greedy
            #and useless as we'll make a refresh later that will invalidate all the cache (and thus the next iteration
            #will fetch all the ids again)
            procurement = self.browse(cr, uid, procurement_id, context=context)
            if procurement.state not in ("running", "done"):
                try:
                    if self._assign(cr, uid, procurement, context=context):
                        res = self._run(cr,
                                        uid,
                                        procurement,
                                        context=context or {})
                        if res:
                            self.write(cr,
                                       uid, [procurement.id],
                                       {'state': 'running'},
                                       context=context)
                        else:
                            self.write(cr,
                                       uid, [procurement.id],
                                       {'state': 'exception'},
                                       context=context)
                    else:
                        self.message_post(
                            cr,
                            uid, [procurement.id],
                            body=_('No rule matching this procurement'),
                            context=context)
                        self.write(cr,
                                   uid, [procurement.id],
                                   {'state': 'exception'},
                                   context=context)
                    if autocommit:
                        cr.commit()
                except OperationalError:
                    if autocommit:
                        cr.rollback()
                        continue
                    else:
                        raise
        return True

    def check(self, cr, uid, ids, autocommit=False, context=None):
        done_ids = []
        for procurement in self.browse(cr, uid, ids, context=context):
            try:
                result = self._check(cr, uid, procurement, context=context)
                if result:
                    done_ids.append(procurement.id)
                if autocommit:
                    cr.commit()
            except OperationalError:
                if autocommit:
                    cr.rollback()
                    continue
                else:
                    raise
        if done_ids:
            self.write(cr, uid, done_ids, {'state': 'done'}, context=context)
        return done_ids

    #
    # Method to overwrite in different procurement modules
    #
    def _find_suitable_rule(self, cr, uid, procurement, context=None):
        '''This method returns a procurement.rule that depicts what to do with the given procurement
        in order to complete its needs. It returns False if no suiting rule is found.
            :param procurement: browse record
            :rtype: int or False
        '''
        return False

    def _assign(self, cr, uid, procurement, context=None):
        '''This method check what to do with the given procurement in order to complete its needs.
        It returns False if no solution is found, otherwise it stores the matching rule (if any) and
        returns True.
            :param procurement: browse record
            :rtype: boolean
        '''
        #if the procurement already has a rule assigned, we keep it (it has a higher priority as it may have been chosen manually)
        if procurement.rule_id:
            return True
        elif procurement.product_id.type not in ('service', 'digital'):
            rule_id = self._find_suitable_rule(cr,
                                               uid,
                                               procurement,
                                               context=context)
            if rule_id:
                self.write(cr,
                           uid, [procurement.id], {'rule_id': rule_id},
                           context=context)
                return True
        return False

    def _run(self, cr, uid, procurement, context=None):
        '''This method implements the resolution of the given procurement
            :param procurement: browse record
            :returns: True if the resolution of the procurement was a success, False otherwise to set it in exception
        '''
        return True

    def _check(self, cr, uid, procurement, context=None):
        '''Returns True if the given procurement is fulfilled, False otherwise
            :param procurement: browse record
            :rtype: boolean
        '''
        return False

    #
    # Scheduler
    #
    def run_scheduler(self,
                      cr,
                      uid,
                      use_new_cursor=False,
                      company_id=False,
                      context=None):
        '''
        Call the scheduler to check the procurement order. This is intented to be done for all existing companies at
        the same time, so we're running all the methods as SUPERUSER to avoid intercompany and access rights issues.

        @param self: The object pointer
        @param cr: The current row, from the database cursor,
        @param uid: The current user ID for security checks
        @param ids: List of selected IDs
        @param use_new_cursor: if set, use a dedicated cursor and auto-commit after processing each procurement.
            This is appropriate for batch jobs only.
        @param context: A standard dictionary for contextual values
        @return:  Dictionary of values
        '''
        if context is None:
            context = {}
        try:
            if use_new_cursor:
                cr = yuancloud.registry(cr.dbname).cursor()

            # Run confirmed procurements
            dom = [('state', '=', 'confirmed')]
            if company_id:
                dom += [('company_id', '=', company_id)]
            prev_ids = []
            while True:
                ids = self.search(cr, SUPERUSER_ID, dom, context=context)
                if not ids or prev_ids == ids:
                    break
                else:
                    prev_ids = ids
                self.run(cr,
                         SUPERUSER_ID,
                         ids,
                         autocommit=use_new_cursor,
                         context=context)
                if use_new_cursor:
                    cr.commit()

            # Check if running procurements are done
            offset = 0
            dom = [('state', '=', 'running')]
            if company_id:
                dom += [('company_id', '=', company_id)]
            prev_ids = []
            while True:
                ids = self.search(cr,
                                  SUPERUSER_ID,
                                  dom,
                                  offset=offset,
                                  context=context)
                if not ids or prev_ids == ids:
                    break
                else:
                    prev_ids = ids
                self.check(cr,
                           SUPERUSER_ID,
                           ids,
                           autocommit=use_new_cursor,
                           context=context)
                if use_new_cursor:
                    cr.commit()

        finally:
            if use_new_cursor:
                try:
                    cr.close()
                except Exception:
                    pass

        return {}
Beispiel #30
0
class fetchmail_server(osv.osv):
    """Incoming POP/IMAP mail server account"""
    _name = 'fetchmail.server'
    _description = "POP/IMAP Server"
    _order = 'priority'

    _columns = {
        'name':
        fields.char('Name', required=True, readonly=False),
        'active':
        fields.boolean('Active', required=False),
        'state':
        fields.selection([
            ('draft', 'Not Confirmed'),
            ('done', 'Confirmed'),
        ],
                         'Status',
                         select=True,
                         readonly=True,
                         copy=False),
        'server':
        fields.char('Server Name',
                    readonly=True,
                    help="Hostname or IP of the mail server",
                    states={'draft': [('readonly', False)]}),
        'port':
        fields.integer('Port',
                       readonly=True,
                       states={'draft': [('readonly', False)]}),
        'type':
        fields.selection([
            ('pop', 'POP Server'),
            ('imap', 'IMAP Server'),
            ('local', 'Local Server'),
        ],
                         'Server Type',
                         select=True,
                         required=True,
                         readonly=False),
        'is_ssl':
        fields.boolean(
            'SSL/TLS',
            help=
            "Connections are encrypted with SSL/TLS through a dedicated port (default: IMAPS=993, POP3S=995)"
        ),
        'attach':
        fields.boolean(
            'Keep Attachments',
            help="Whether attachments should be downloaded. "
            "If not enabled, incoming emails will be stripped of any attachments before being processed"
        ),
        'original':
        fields.boolean(
            'Keep Original',
            help=
            "Whether a full original copy of each email should be kept for reference"
            "and attached to each processed message. This will usually double the size of your message database."
        ),
        'date':
        fields.datetime('Last Fetch Date', readonly=True),
        'user':
        fields.char('Username',
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'password':
        fields.char('Password',
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'action_id':
        fields.many2one(
            'ir.actions.server',
            'Server Action',
            help=
            "Optional custom server action to trigger for each incoming mail, "
            "on the record that was created or updated by this mail"),
        'object_id':
        fields.many2one(
            'ir.model',
            "Create a New Record",
            help="Process each incoming mail as part of a conversation "
            "corresponding to this document type. This will create "
            "new documents for new conversations, or attach follow-up "
            "emails to the existing conversations (documents)."),
        'priority':
        fields.integer('Server Priority',
                       readonly=True,
                       states={'draft': [('readonly', False)]},
                       help="Defines the order of processing, "
                       "lower values mean higher priority"),
        'message_ids':
        fields.one2many('mail.mail',
                        'fetchmail_server_id',
                        'Messages',
                        readonly=True),
        'configuration':
        fields.text('Configuration', readonly=True),
        'script':
        fields.char('Script', readonly=True),
    }
    _defaults = {
        'state': "draft",
        'type': "pop",
        'active': True,
        'priority': 5,
        'attach': True,
        'script': '/mail/static/scripts/yuancloud_mailgate.py',
    }

    def onchange_server_type(self,
                             cr,
                             uid,
                             ids,
                             server_type=False,
                             ssl=False,
                             object_id=False):
        port = 0
        values = {}
        if server_type == 'pop':
            port = ssl and 995 or 110
        elif server_type == 'imap':
            port = ssl and 993 or 143
        else:
            values['server'] = ''
        values['port'] = port

        conf = {
            'dbname': cr.dbname,
            'uid': uid,
            'model': 'MODELNAME',
        }
        if object_id:
            m = self.pool.get('ir.model')
            r = m.read(cr, uid, [object_id], ['model'])
            conf['model'] = r[0]['model']
        values[
            'configuration'] = """Use the below script with the following command line options with your Mail Transport Agent (MTA)

yuancloud_mailgate.py --host=HOSTNAME --port=PORT -u %(uid)d -p PASSWORD -d %(dbname)s

Example configuration for the postfix mta running locally:

/etc/postfix/virtual_aliases:
@youdomain yuancloud_mailgate@localhost

/etc/aliases:
yuancloud_mailgate: "|/path/to/yuancloud-mailgate.py --host=localhost -u %(uid)d -p PASSWORD -d %(dbname)s"

""" % conf

        return {'value': values}

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

    @api.cr_uid_ids_context
    def connect(self, cr, uid, server_id, context=None):
        if isinstance(server_id, (list, tuple)):
            server_id = server_id[0]
        server = self.browse(cr, uid, server_id, context)
        if server.type == 'imap':
            if server.is_ssl:
                connection = IMAP4_SSL(server.server, int(server.port))
            else:
                connection = IMAP4(server.server, int(server.port))
            connection.login(server.user, server.password)
        elif server.type == 'pop':
            if server.is_ssl:
                connection = POP3_SSL(server.server, int(server.port))
            else:
                connection = POP3(server.server, int(server.port))
            #TODO: use this to remove only unread messages
            #connection.user("recent:"+server.user)
            connection.user(server.user)
            connection.pass_(server.password)
        # Add timeout on socket
        connection.sock.settimeout(MAIL_TIMEOUT)
        return connection

    def button_confirm_login(self, cr, uid, ids, context=None):
        if context is None:
            context = {}
        for server in self.browse(cr, uid, ids, context=context):
            try:
                connection = server.connect()
                server.write({'state': 'done'})
            except Exception, e:
                _logger.info("Failed to connect to %s server %s.",
                             server.type,
                             server.name,
                             exc_info=True)
                raise UserError(
                    _("Connection test failed: %s") % tools.ustr(e))
            finally: