Beispiel #1
0
class mrp_production(osv.osv):
    _inherit = 'mrp.production'
    _columns = {
        'allow_reorder':
        fields.boolean(
            'Free Serialisation',
            help=
            "Check this to be able to move independently all production orders, without moving dependent ones."
        ),
    }

    def _production_date_end(self,
                             cr,
                             uid,
                             ids,
                             prop,
                             unknow_none,
                             context=None):
        """ Calculates planned end date of production order.
        @return: Dictionary of values
        """
        result = {}
        for prod in self.browse(cr, uid, ids, context=context):
            result[prod.id] = prod.date_planned
            for line in prod.workcenter_lines:
                result[prod.id] = max(line.date_planned_end, result[prod.id])
        return result

    def action_production_end(self, cr, uid, ids, context=None):
        """ Finishes work order if production order is done.
        @return: Super method
        """
        obj = self.browse(cr, uid, ids, context=context)[0]
        workcenter_pool = self.pool.get('mrp.production.workcenter.line')
        for workcenter_line in obj.workcenter_lines:
            if workcenter_line.state == 'draft':
                workcenter_line.signal_workflow('button_start_working')
            workcenter_line.signal_workflow('button_done')
        return super(mrp_production,
                     self).action_production_end(cr, uid, ids, context=context)

    def action_in_production(self, cr, uid, ids, context=None):
        """ Changes state to In Production and writes starting date.
        @return: True
        """
        workcenter_pool = self.pool.get('mrp.production.workcenter.line')
        for prod in self.browse(cr, uid, ids):
            if prod.workcenter_lines:
                workcenter_pool.signal_workflow(cr, uid,
                                                [prod.workcenter_lines[0].id],
                                                'button_start_working')
        return super(mrp_production,
                     self).action_in_production(cr, uid, ids, context=context)

    def action_cancel(self, cr, uid, ids, context=None):
        """ Cancels work order if production order is canceled.
        @return: Super method
        """
        workcenter_pool = self.pool.get('mrp.production.workcenter.line')
        obj = self.browse(cr, uid, ids, context=context)[0]
        workcenter_pool.signal_workflow(
            cr, uid, [record.id for record in obj.workcenter_lines],
            'button_cancel')
        return super(mrp_production, self).action_cancel(cr,
                                                         uid,
                                                         ids,
                                                         context=context)

    def _compute_planned_workcenter(self,
                                    cr,
                                    uid,
                                    ids,
                                    context=None,
                                    mini=False):
        """ Computes planned and finished dates for work order.
        @return: Calculated date
        """
        dt_end = datetime.now()
        if context is None:
            context = {}
        for po in self.browse(cr, uid, ids, context=context):
            dt_end = datetime.strptime(po.date_planned, '%Y-%m-%d %H:%M:%S')
            if not po.date_start:
                self.write(cr,
                           uid, [po.id], {'date_start': po.date_planned},
                           context=context,
                           update=False)
            old = None
            for wci in range(len(po.workcenter_lines)):
                wc = po.workcenter_lines[wci]
                if (old is None) or (wc.sequence > old):
                    dt = dt_end
                if context.get('__last_update'):
                    del context['__last_update']
                if (wc.date_planned <
                        dt.strftime('%Y-%m-%d %H:%M:%S')) or mini:
                    self.pool.get('mrp.production.workcenter.line').write(
                        cr,
                        uid, [wc.id],
                        {'date_planned': dt.strftime('%Y-%m-%d %H:%M:%S')},
                        context=context,
                        update=False)
                    i = self.pool.get('resource.calendar').interval_get(
                        cr,
                        uid,
                        #passing False makes resource_resource._schedule_hours run 1000 iterations doing nothing
                        wc.workcenter_id.calendar_id
                        and wc.workcenter_id.calendar_id.id or None,
                        dt,
                        wc.hour or 0.0)
                    if i:
                        dt_end = max(dt_end, i[-1][1])
                else:
                    dt_end = datetime.strptime(wc.date_planned_end,
                                               '%Y-%m-%d %H:%M:%S')

                old = wc.sequence or 0
            super(mrp_production, self).write(cr, uid, [po.id],
                                              {'date_finished': dt_end})
        return dt_end

    def _move_pass(self, cr, uid, ids, context=None):
        """ Calculates start date for stock moves finding interval from resource calendar.
        @return: True
        """
        for po in self.browse(cr, uid, ids, context=context):
            if po.allow_reorder:
                continue
            todo = list(po.move_lines)
            dt = datetime.strptime(po.date_start, '%Y-%m-%d %H:%M:%S')
            while todo:
                l = todo.pop(0)
                if l.state in ('done', 'cancel', 'draft'):
                    continue
                todo += l.move_dest_id_lines
                date_end = l.production_id.date_finished
                if date_end and datetime.strptime(date_end,
                                                  '%Y-%m-%d %H:%M:%S') > dt:
                    if l.production_id.state not in ('done', 'cancel'):
                        for wc in l.production_id.workcenter_lines:
                            i = self.pool.get(
                                'resource.calendar').interval_min_get(
                                    cr, uid, wc.workcenter_id.calendar_id.id
                                    or False, dt, wc.hour or 0.0)
                            dt = i[0][0]
                        if l.production_id.date_start > dt.strftime(
                                '%Y-%m-%d %H:%M:%S'):
                            self.write(cr,
                                       uid, [l.production_id.id], {
                                           'date_start':
                                           dt.strftime('%Y-%m-%d %H:%M:%S')
                                       },
                                       mini=True)
        return True

    def _move_futur(self, cr, uid, ids, context=None):
        """ Calculates start date for stock moves.
        @return: True
        """
        for po in self.browse(cr, uid, ids, context=context):
            if po.allow_reorder:
                continue
            for line in po.move_created_ids:
                l = line
                while l.move_dest_id:
                    l = l.move_dest_id
                    if l.state in ('done', 'cancel', 'draft'):
                        break
                    if l.production_id.state in ('done', 'cancel'):
                        break
                    if l.production_id and (l.production_id.date_start <
                                            po.date_finished):
                        self.write(cr, uid, [l.production_id.id],
                                   {'date_start': po.date_finished})
                        break
        return True

    def write(self, cr, uid, ids, vals, context=None, update=True, mini=True):
        direction = {}
        if vals.get('date_start', False):
            for po in self.browse(cr, uid, ids, context=context):
                direction[po.id] = cmp(po.date_start,
                                       vals.get('date_start', False))
        result = super(mrp_production, self).write(cr,
                                                   uid,
                                                   ids,
                                                   vals,
                                                   context=context)
        if (vals.get('workcenter_lines', False)
                or vals.get('date_start', False)
                or vals.get('date_planned', False)) and update:
            self._compute_planned_workcenter(cr,
                                             uid,
                                             ids,
                                             context=context,
                                             mini=mini)
        for d in direction:
            if direction[d] == 1:
                # the production order has been moved to the passed
                self._move_pass(cr, uid, [d], context=context)
                pass
            elif direction[d] == -1:
                self._move_futur(cr, uid, [d], context=context)
                # the production order has been moved to the future
                pass
        return result

    def action_compute(self, cr, uid, ids, properties=None, context=None):
        """ Computes bills of material of a product and planned date of work order.
        @param properties: List containing dictionaries of properties.
        @return: No. of products.
        """
        result = super(mrp_production,
                       self).action_compute(cr,
                                            uid,
                                            ids,
                                            properties=properties,
                                            context=context)
        self._compute_planned_workcenter(cr, uid, ids, context=context)
        return result
Beispiel #2
0
class PaymentAcquirer(osv.Model):
    """ Acquirer Model. Each specific acquirer can extend the model by adding
    its own fields, using the acquirer_name as a prefix for the new fields.
    Using the required_if_provider='<name>' attribute on fields it is possible
    to have required fields that depend on a specific acquirer.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        All templates will receive:

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

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

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

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

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

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


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

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

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

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

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

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

    def s2s_validate(self, cr, uid, id, data, context=None):
        acquirer = self.browse(cr, uid, id, context=context)
        cust_method_name = '%s_s2s_form_validate' % (acquirer.provider)
        if hasattr(self, cust_method_name):
            method = getattr(self, cust_method_name)
            return method(cr, uid, id, data, context=context)
        return True
Beispiel #3
0
class procurement_rule(osv.osv):
    _inherit = 'procurement.rule'

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

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

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

    _defaults = {
        'procure_method': 'make_to_stock',
        'propagate': True,
        'delay': 0,
    }
Beispiel #4
0
class config(osv.Model):
    _name = 'google.drive.config'
    _description = "Google Drive templates config"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def get_google_scope(self):
        return 'https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file'
Beispiel #5
0
class hr_payslip_run(osv.osv):

    _inherit = 'hr.payslip.run'
    _description = 'Payslip Batches'
    _columns = {
        'available_advice':
        fields.boolean(
            'Made Payment Advice?',
            help=
            "If this box is checked which means that Payment Advice exists for current batch",
            readonly=False,
            copy=False),
    }

    def draft_payslip_run(self, cr, uid, ids, context=None):
        res = super(hr_payslip_run, self).draft_payslip_run(cr,
                                                            uid,
                                                            ids,
                                                            context=context)
        self.write(cr, uid, ids, {'available_advice': False}, context=context)
        return res

    def create_advice(self, cr, uid, ids, context=None):
        payslip_pool = self.pool.get('hr.payslip')
        payslip_line_pool = self.pool.get('hr.payslip.line')
        advice_pool = self.pool.get('hr.payroll.advice')
        advice_line_pool = self.pool.get('hr.payroll.advice.line')
        users = self.pool.get('res.users').browse(cr,
                                                  uid, [uid],
                                                  context=context)
        for run in self.browse(cr, uid, ids, context=context):
            if run.available_advice:
                raise UserError(
                    _("Payment advice already exists for %s, 'Set to Draft' to create a new advice."
                      ) % (run.name, ))
            advice_data = {
                'batch_id':
                run.id,
                'company_id':
                users[0].company_id.id,
                'name':
                run.name,
                'date':
                run.date_end,
                'bank_id':
                users[0].company_id.partner_id.bank_ids
                and users[0].company_id.partner_id.bank_ids[0].id or False
            }
            advice_id = advice_pool.create(cr,
                                           uid,
                                           advice_data,
                                           context=context)
            slip_ids = []
            for slip_id in run.slip_ids:
                # TODO is it necessary to interleave the calls ?
                payslip_pool.signal_workflow(cr, uid, [slip_id.id],
                                             'hr_verify_sheet')
                payslip_pool.signal_workflow(cr, uid, [slip_id.id],
                                             'process_sheet')
                slip_ids.append(slip_id.id)

            for slip in payslip_pool.browse(cr, uid, slip_ids,
                                            context=context):
                if not slip.employee_id.bank_account_id or not slip.employee_id.bank_account_id.acc_number:
                    raise UserError(
                        _('Please define bank account for the %s employee') %
                        (slip.employee_id.name))
                line_ids = payslip_line_pool.search(cr,
                                                    uid,
                                                    [('slip_id', '=', slip.id),
                                                     ('code', '=', 'NET')],
                                                    context=context)
                if line_ids:
                    line = payslip_line_pool.browse(cr,
                                                    uid,
                                                    line_ids,
                                                    context=context)[0]
                    advice_line = {
                        'advice_id': advice_id,
                        'name': slip.employee_id.bank_account_id.acc_number,
                        'employee_id': slip.employee_id.id,
                        'bysal': line.total
                    }
                    advice_line_pool.create(cr,
                                            uid,
                                            advice_line,
                                            context=context)
        return self.write(cr, uid, ids, {'available_advice': True})
Beispiel #6
0
class restaurant_table(osv.osv):
    _name = 'restaurant.table'
    _columns = {
        'name':
        fields.char('Table Name',
                    size=32,
                    required=True,
                    help='An internal identification of a table'),
        'floor_id':
        fields.many2one('restaurant.floor', 'Floor'),
        'shape':
        fields.selection([('square', 'Square'), ('round', 'Round')],
                         'Shape',
                         required=True),
        'position_h':
        fields.float(
            'Horizontal Position',
            help=
            "The table's horizontal position from the left side to the table's center, in pixels"
        ),
        'position_v':
        fields.float(
            'Vertical Position',
            help=
            "The table's vertical position from the top to the table's center, in pixels"
        ),
        'width':
        fields.float('Width', help="The table's width in pixels"),
        'height':
        fields.float('Height', help="The table's height in pixels"),
        'seats':
        fields.integer(
            'Seats',
            help="The default number of customer served at this table."),
        'color':
        fields.char(
            'Color',
            help=
            "The table's color, expressed as a valid 'background' CSS property value"
        ),
        'active':
        fields.boolean(
            'Active',
            help=
            'If false, the table is deactivated and will not be available in the point of sale'
        ),
        'pos_order_ids':
        fields.one2many('pos.order',
                        'table_id',
                        'Pos Orders',
                        help='The orders served at this table'),
    }

    _defaults = {
        'shape': 'square',
        'seats': 1,
        'position_h': 10,
        'position_v': 10,
        'height': 50,
        'width': 50,
        'active': True,
    }

    def create_from_ui(self, cr, uid, table, context=None):
        """ create or modify a table from the point of sale UI.
            table contains the table's fields. If it contains an
            id, it will modify the existing table. It then 
            returns the id of the table.  """

        if table.get('floor_id', False):
            floor_id = table['floor_id'][0]
            table['floor_id'] = floor_id

        if table.get('id', False):  # Modifiy existing table
            table_id = table['id']
            del table['id']
            self.write(cr, uid, [table_id], table, context=context)
        else:
            table_id = self.create(cr, uid, table, context=context)

        return table_id
Beispiel #7
0
class consult_export_csv_excel_reportcustomer(osv.osv_memory):
    _name = 'consult.export.csv.excel.reportcustomer'
    _description = 'Exportar Reporte a Excel o CSV'
    _columns = {
        'datas_fname':
        fields.char('File Name', size=256),
        'file':
        fields.binary('Layout'),
        'download_file':
        fields.boolean('Descargar Archivo'),
        'cadena_decoding':
        fields.text('Binario sin encoding'),
        'type':
        fields.selection(
            [('csv', 'CSV')],
            'Tipo Exportacion',
            required=False,
        ),
    }

    _defaults = {
        'download_file': False,
        'type': 'csv',
    }

    def export_csv_file(self, cr, uid, ids, context=None):
        document_csv = ""
        active_ids = context['active_ids']
        consult_obj = self.pool.get('stock.reportcustomer.model')
        if active_ids:
            for active in active_ids:
                da_list = []
                for rec in self.browse(cr, uid, ids, context=None):
                    consult_br = consult_obj.browse(cr,
                                                    uid,
                                                    active,
                                                    context=None)
                    da_list.append(consult_br.date)
                    salto_line = "\n"
                    cabeceras_p = "Cliente" + "," + "Fecha Inicio" + "," + "Fecha Fin"
                    document_csv = document_csv + cabeceras_p

                    linea_1 = consult_br.name.name+","+\
                    consult_br.date+","+consult_br.date_end

                    document_csv = document_csv + salto_line + linea_1 + salto_line

                    cabeceras_l = "Factura" + "," + "Fecha" + "," + "Monto"

                    texto_x = "Facturas del Cliente" + "," + ","
                    document_csv = document_csv + salto_line + texto_x

                    document_csv = document_csv + salto_line + cabeceras_l

                    detalle_lineas = ""
                    for linea in consult_br.reportcustomer_invoice_lines:
                        linea_str = ""
                        if linea.invoice_id:
                            linea_str = str(linea.invoice_id.number)+","+str(linea.invoice_id.date_invoice)+\
                            ","+str(linea.invoice_id.amount_total)

                        detalle_lineas = detalle_lineas + salto_line + linea_str
                    document_csv = document_csv + detalle_lineas + salto_line + salto_line

                    cabeceras_l = "Producto"+","+"Cantidad"+","+\
                    "Unidad de Medida"+","+"Total Facturado"

                    texto_x = "Detalle Productos Facturados" + "," + "," + ","
                    document_csv = document_csv + salto_line + texto_x

                    document_csv = document_csv + salto_line + cabeceras_l

                    detalle_lineas = ""
                    for linea in consult_br.reportcustomer_lines:
                        linea_str = ""
                        if linea.product_id:
                            linea_str = str(linea.product_id.name)+\
                            ","+str(linea.qty)+","+str(linea.uom_id.name)+","+str(linea.amount_total)

                        detalle_lineas = detalle_lineas + salto_line + linea_str
                    document_csv = document_csv + detalle_lineas + salto_line + salto_line

                date = datetime.now().strftime('%d-%m-%Y')
                if len(da_list) > 1:
                    datas_fname = "Reporte Facturacion Client " + str(
                        date) + ".csv"  # Nombre del Archivo
                else:
                    datas_fname = "Reporte Facturacion Client " + consult_br.date + " - " + consult_br.date_end + ".csv"  # Nombre del Archivo
                rec.write({
                    'cadena_decoding': document_csv,
                    'datas_fname': datas_fname,
                    'file': base64.encodestring(document_csv),
                    'download_file': True
                })
        return {
            'type': 'ir.actions.act_window',
            'res_model': 'consult.export.csv.excel.reportcustomer',
            'view_mode': 'form',
            'view_type': 'form',
            'res_id': ids[0],
            'views': [(False, 'form')],
            'target': 'new',
        }

    def process_export(self, cr, uid, ids, context=None):
        for rec in self.browse(cr, uid, ids, context=context):
            if rec.type == 'csv':
                result = self.export_csv_file(cr, uid, ids, context=context)
                return result
        return True
Beispiel #8
0
class actions_server(osv.Model):
    """ Add website option in server actions. """
    _name = 'ir.actions.server'
    _inherit = ['ir.actions.server']

    def _compute_website_url(self,
                             cr,
                             uid,
                             id,
                             website_path,
                             xml_id,
                             context=None):
        base_url = self.pool['ir.config_parameter'].get_param(cr,
                                                              uid,
                                                              'web.base.url',
                                                              context=context)
        link = website_path or xml_id or (id and '%d' % id) or ''
        if base_url and link:
            path = '%s/%s' % ('/website/action', link)
            return '%s' % urlparse.urljoin(base_url, path)
        return ''

    def _get_website_url(self, cr, uid, ids, name, args, context=None):
        res = dict.fromkeys(ids, False)
        for action in self.browse(cr, uid, ids, context=context):
            if action.state == 'code' and action.website_published:
                res[action.id] = self._compute_website_url(cr,
                                                           uid,
                                                           action.id,
                                                           action.website_path,
                                                           action.xml_id,
                                                           context=context)
        return res

    _columns = {
        'xml_id':
        fields.function(osv.osv.get_xml_id,
                        type='char',
                        string="External ID",
                        help="ID of the action if defined in a XML file"),
        'website_path':
        fields.char('Website Path'),
        'website_url':
        fields.function(
            _get_website_url,
            type='char',
            string='Website URL',
            help='The full URL to access the server action through the website.'
        ),
        'website_published':
        fields.boolean(
            'Available on the Website',
            copy=False,
            help=
            'A code server action can be executed from the website, using a dedicated'
            'controller. The address is <base>/website/action/<website_path>.'
            'Set this field as True to allow users to run this action. If it'
            'set to is False the action cannot be run through the website.'),
    }

    def on_change_website_path(self,
                               cr,
                               uid,
                               ids,
                               website_path,
                               xml_id,
                               context=None):
        values = {
            'website_url':
            self._compute_website_url(cr,
                                      uid,
                                      ids and ids[0] or None,
                                      website_path,
                                      xml_id,
                                      context=context)
        }
        return {'value': values}

    def _get_eval_context(self, cr, uid, action, context=None):
        """ Override to add the request object in eval_context. """
        eval_context = super(actions_server,
                             self)._get_eval_context(cr,
                                                     uid,
                                                     action,
                                                     context=context)
        if action.state == 'code':
            eval_context['request'] = request
        return eval_context

    def run_action_code_multi(self,
                              cr,
                              uid,
                              action,
                              eval_context=None,
                              context=None):
        """ Override to allow returning response the same way action is already
        returned by the basic server action behavior. Note that response has
        priority over action, avoid using both. """
        res = super(actions_server,
                    self).run_action_code_multi(cr, uid, action, eval_context,
                                                context)
        if 'response' in eval_context:
            return eval_context['response']
        return res
Beispiel #9
0
class base_config_settings(osv.osv_memory):
    _name = 'base.config.settings'
    _inherit = 'res.config.settings'

    _columns = {
        'group_light_multi_company':
        fields.boolean(
            'Manage multiple companies',
            help=
            'Work in multi-company environments, with appropriate security access between companies.',
            implied_group='base.group_light_multi_company'),
        'module_share':
        fields.boolean('Allow documents sharing',
                       help="""Share or embbed any screen of eCore."""),
        'module_portal':
        fields.boolean(
            'Activate the customer portal',
            help="""Give your customers access to their documents."""),
        'module_auth_oauth':
        fields.boolean(
            'Use external authentication providers, sign in with Google...'),
        'module_base_import':
        fields.boolean(
            "Allow users to import data from CSV/XLS/XLSX/ODS files"),
        'module_google_drive':
        fields.boolean('Attach Google documents to any record',
                       help="""This installs the module google_docs."""),
        'module_google_calendar':
        fields.boolean(
            'Allow the users to synchronize their calendar  with Google Calendar',
            help="""This installs the module google_calendar."""),
        'module_inter_company_rules':
        fields.boolean(
            'Manage Inter Company',
            help=
            """This installs the module inter_company_rules.\n Configure company rules to automatically create SO/PO when one of your company sells/buys to another of your company."""
        ),
        'company_share_partner':
        fields.boolean(
            'Share partners to all companies',
            help=
            "Share your partners to all companies defined in your instance.\n"
            " * Checked : Partners are visible for every companies, even if a company is defined on the partner.\n"
            " * Unchecked : Each company can see only its partner (partners where company is defined). Partners not related to a company are visible for all companies."
        ),
    }

    def open_company(self, cr, uid, ids, context=None):
        user = self.pool.get('res.users').browse(cr, uid, uid, context)
        return {
            'type': 'ir.actions.act_window',
            'name': 'My Company',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'res.company',
            'res_id': user.company_id.id,
            'target': 'current',
        }

    def get_default_company_share_partner(self,
                                          cr,
                                          uid,
                                          ids,
                                          fields,
                                          context=None):
        partner_rule = self.pool['ir.model.data'].xmlid_to_object(
            cr, uid, 'base.res_partner_rule', context=context)
        return {'company_share_partner': not bool(partner_rule.active)}

    def set_default_company_share_partner(self, cr, uid, ids, context=None):
        partner_rule = self.pool['ir.model.data'].xmlid_to_object(
            cr, uid, 'base.res_partner_rule', context=context)
        for wizard in self.browse(cr, uid, ids, context=context):
            self.pool['ir.rule'].write(
                cr,
                uid, [partner_rule.id],
                {'active': not bool(wizard.company_share_partner)},
                context=context)
Beispiel #10
0
class sale_order(osv.osv):
    _inherit = 'sale.order'

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

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

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

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

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

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

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

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

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

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

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

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

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

        return action

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

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

    def create(self, cr, uid, values, context=None):
        if not values.get('template_id'):
            defaults = self.default_get(cr,
                                        uid, ['template_id'],
                                        context=context)
            template_values = self.onchange_template_id(
                cr,
                uid, [],
                defaults.get('template_id'),
                partner=values.get('partner_id'),
                fiscal_position_id=values.get('fiscal_position'),
                context=context).get('value', {})
            values = dict(template_values, **values)
        return super(sale_order, self).create(cr, uid, values, context=context)
Beispiel #11
0
class crm_configuration(osv.TransientModel):
    _name = 'sale.config.settings'
    _inherit = ['sale.config.settings', 'fetchmail.config.settings']

    _columns = {
        'generate_sales_team_alias':
        fields.boolean(
            "Automatically generate an email alias at the sales team creation",
            help=
            "eCore will generate an email alias based on the sales team name"),
        'alias_prefix':
        fields.char('Default Alias Name for Leads'),
        'alias_domain':
        fields.char('Alias Domain'),
        'group_use_lead':
        fields.selection([
            (0, "Each mail sent to the alias creates a new opportunity"),
            (1,
             "Use leads if you need a qualification step before creating an opportunity or a customer"
             )
        ],
                         "Leads",
                         implied_group='crm.group_use_lead'),
        'module_crm_voip':
        fields.boolean("VoIP integration", help="Integration with Asterisk"),
        'module_website_sign':
        fields.boolean("eCore Sign"),
    }

    _defaults = {
        'alias_domain':
        lambda self, cr, uid, context: self.pool["ir.config_parameter"].
        get_param(cr, uid, "mail.catchall.domain", context=context),
    }

    def _find_default_lead_alias_id(self, cr, uid, context=None):
        alias_id = self.pool['ir.model.data'].xmlid_to_res_id(
            cr, uid, 'crm.mail_alias_lead_info')
        if not alias_id:
            alias_ids = self.pool['mail.alias'].search(
                cr,
                uid, [('alias_model_id.model', '=', 'crm.lead'),
                      ('alias_force_thread_id', '=', False),
                      ('alias_parent_model_id.model', '=', 'crm.team'),
                      ('alias_parent_thread_id', '=', False),
                      ('alias_defaults', '=', '{}')],
                context=context)
            alias_id = alias_ids and alias_ids[0] or False
        return alias_id

    def get_default_generate_sales_team_alias(self,
                                              cr,
                                              uid,
                                              ids,
                                              context=None):
        return {
            'generate_sales_team_alias':
            self.pool['ir.values'].get_default(cr, uid,
                                               'sales.config.settings',
                                               'generate_sales_team_alias')
        }

    def set_default_generate_sales_team_alias(self,
                                              cr,
                                              uid,
                                              ids,
                                              context=None):
        config_value = self.browse(cr, uid, ids,
                                   context=context).generate_sales_team_alias
        self.pool['ir.values'].set_default(cr, uid, 'sales.config.settings',
                                           'generate_sales_team_alias',
                                           config_value)

    def get_default_alias_prefix(self, cr, uid, ids, context=None):
        alias_name = False
        alias_id = self._find_default_lead_alias_id(cr, uid, context=context)
        if alias_id:
            alias_name = self.pool['mail.alias'].browse(
                cr, uid, alias_id, context=context).alias_name
        return {'alias_prefix': alias_name}

    def set_default_alias_prefix(self, cr, uid, ids, context=None):
        mail_alias = self.pool['mail.alias']
        for record in self.browse(cr, uid, ids, context=context):
            alias_id = self._find_default_lead_alias_id(cr,
                                                        uid,
                                                        context=context)
            if not alias_id:
                create_ctx = dict(context,
                                  alias_model_name='crm.lead',
                                  alias_parent_model_name='crm.team')
                alias_id = self.pool['mail.alias'].create(
                    cr,
                    uid, {'alias_name': record.alias_prefix},
                    context=create_ctx)
            else:
                mail_alias.write(cr,
                                 uid,
                                 alias_id, {'alias_name': record.alias_prefix},
                                 context=context)
        return True
Beispiel #12
0
class MergePartnerAutomatic(osv.TransientModel):
    """
        The idea behind this wizard is to create a list of potential partners to
        merge. We use two objects, the first one is the wizard for the end-user.
        And the second will contain the partner list to merge.
    """
    _name = 'base.partner.merge.automatic.wizard'

    _columns = {
        # Group by
        'group_by_email':
        fields.boolean('Email'),
        'group_by_name':
        fields.boolean('Name'),
        'group_by_is_company':
        fields.boolean('Is Company'),
        'group_by_vat':
        fields.boolean('VAT'),
        'group_by_parent_id':
        fields.boolean('Parent Company'),
        'state':
        fields.selection([('option', 'Option'), ('selection', 'Selection'),
                          ('finished', 'Finished')],
                         'State',
                         readonly=True,
                         required=True),
        'number_group':
        fields.integer("Group of Contacts", readonly=True),
        'current_line_id':
        fields.many2one('base.partner.merge.line', 'Current Line'),
        'line_ids':
        fields.one2many('base.partner.merge.line', 'wizard_id', 'Lines'),
        'partner_ids':
        fields.many2many('res.partner', string='Contacts'),
        'dst_partner_id':
        fields.many2one('res.partner', string='Destination Contact'),
        'exclude_contact':
        fields.boolean('A user associated to the contact'),
        'exclude_journal_item':
        fields.boolean('Journal Items associated to the contact'),
        'maximum_group':
        fields.integer("Maximum of Group of Contacts"),
    }

    def default_get(self, cr, uid, fields, context=None):
        if context is None:
            context = {}
        res = super(MergePartnerAutomatic,
                    self).default_get(cr, uid, fields, context)
        if context.get('active_model') == 'res.partner' and context.get(
                'active_ids'):
            partner_ids = context['active_ids']
            res['state'] = 'selection'
            res['partner_ids'] = partner_ids
            res['dst_partner_id'] = self._get_ordered_partner(
                cr, uid, partner_ids, context=context)[-1].id
        return res

    _defaults = {'state': 'option'}

    def get_fk_on(self, cr, table):
        q = """  SELECT cl1.relname as table,
                        att1.attname as column
                   FROM pg_constraint as con, pg_class as cl1, pg_class as cl2,
                        pg_attribute as att1, pg_attribute as att2
                  WHERE con.conrelid = cl1.oid
                    AND con.confrelid = cl2.oid
                    AND array_lower(con.conkey, 1) = 1
                    AND con.conkey[1] = att1.attnum
                    AND att1.attrelid = cl1.oid
                    AND cl2.relname = %s
                    AND att2.attname = 'id'
                    AND array_lower(con.confkey, 1) = 1
                    AND con.confkey[1] = att2.attnum
                    AND att2.attrelid = cl2.oid
                    AND con.contype = 'f'
        """
        return cr.execute(q, (table, ))

    def _update_foreign_keys(self,
                             cr,
                             uid,
                             src_partners,
                             dst_partner,
                             context=None):
        _logger.debug(
            '_update_foreign_keys for dst_partner: %s for src_partners: %r',
            dst_partner.id, list(map(operator.attrgetter('id'), src_partners)))

        # find the many2one relation to a partner
        proxy = self.pool.get('res.partner')
        self.get_fk_on(cr, 'res_partner')

        # ignore two tables

        for table, column in cr.fetchall():
            if 'base_partner_merge_' in table:
                continue
            partner_ids = tuple(map(int, src_partners))

            query = "SELECT column_name FROM information_schema.columns WHERE table_name LIKE '%s'" % (
                table)
            cr.execute(query, ())
            columns = []
            for data in cr.fetchall():
                if data[0] != column:
                    columns.append(data[0])

            query_dic = {
                'table': table,
                'column': column,
                'value': columns[0],
            }
            if len(columns) <= 1:
                # unique key treated
                query = """
                    UPDATE "%(table)s" as ___tu
                    SET %(column)s = %%s
                    WHERE
                        %(column)s = %%s AND
                        NOT EXISTS (
                            SELECT 1
                            FROM "%(table)s" as ___tw
                            WHERE
                                %(column)s = %%s AND
                                ___tu.%(value)s = ___tw.%(value)s
                        )""" % query_dic
                for partner_id in partner_ids:
                    cr.execute(query,
                               (dst_partner.id, partner_id, dst_partner.id))
            else:
                try:
                    with mute_logger('ecore.sql_db'), cr.savepoint():
                        query = 'UPDATE "%(table)s" SET %(column)s = %%s WHERE %(column)s IN %%s' % query_dic
                        cr.execute(query, (
                            dst_partner.id,
                            partner_ids,
                        ))

                        if column == proxy._parent_name and table == 'res_partner':
                            query = """
                                WITH RECURSIVE cycle(id, parent_id) AS (
                                        SELECT id, parent_id FROM res_partner
                                    UNION
                                        SELECT  cycle.id, res_partner.parent_id
                                        FROM    res_partner, cycle
                                        WHERE   res_partner.id = cycle.parent_id AND
                                                cycle.id != cycle.parent_id
                                )
                                SELECT id FROM cycle WHERE id = parent_id AND id = %s
                            """
                            cr.execute(query, (dst_partner.id, ))
                except psycopg2.Error:
                    # updating fails, most likely due to a violated unique constraint
                    # keeping record with nonexistent partner_id is useless, better delete it
                    query = 'DELETE FROM %(table)s WHERE %(column)s = %%s' % query_dic
                    cr.execute(query, (partner_id, ))

    def _update_reference_fields(self,
                                 cr,
                                 uid,
                                 src_partners,
                                 dst_partner,
                                 context=None):
        _logger.debug(
            '_update_reference_fields for dst_partner: %s for src_partners: %r',
            dst_partner.id, list(map(operator.attrgetter('id'), src_partners)))

        def update_records(model,
                           src,
                           field_model='model',
                           field_id='res_id',
                           context=None):
            proxy = self.pool.get(model)
            if proxy is None:
                return
            domain = [(field_model, '=', 'res.partner'),
                      (field_id, '=', src.id)]
            ids = proxy.search(cr, ecore.SUPERUSER_ID, domain, context=context)
            try:
                with mute_logger('ecore.sql_db'), cr.savepoint():
                    return proxy.write(cr,
                                       ecore.SUPERUSER_ID,
                                       ids, {field_id: dst_partner.id},
                                       context=context)
            except psycopg2.Error:
                # updating fails, most likely due to a violated unique constraint
                # keeping record with nonexistent partner_id is useless, better delete it
                return proxy.unlink(cr,
                                    ecore.SUPERUSER_ID,
                                    ids,
                                    context=context)

        update_records = functools.partial(update_records, context=context)

        for partner in src_partners:
            update_records('calendar',
                           src=partner,
                           field_model='model_id.model')
            update_records('ir.attachment',
                           src=partner,
                           field_model='res_model')
            update_records('mail.followers',
                           src=partner,
                           field_model='res_model')
            update_records('mail.message', src=partner)
            update_records('marketing.campaign.workitem',
                           src=partner,
                           field_model='object_id.model')
            update_records('ir.model.data', src=partner)

        proxy = self.pool['ir.model.fields']
        domain = [('ttype', '=', 'reference')]
        record_ids = proxy.search(cr,
                                  ecore.SUPERUSER_ID,
                                  domain,
                                  context=context)

        for record in proxy.browse(cr,
                                   ecore.SUPERUSER_ID,
                                   record_ids,
                                   context=context):
            try:
                proxy_model = self.pool[record.model]
                column = proxy_model._columns[record.name]
            except KeyError:
                # unknown model or field => skip
                continue

            if isinstance(column, fields.function):
                continue

            for partner in src_partners:
                domain = [(record.name, '=', 'res.partner,%d' % partner.id)]
                model_ids = proxy_model.search(cr,
                                               ecore.SUPERUSER_ID,
                                               domain,
                                               context=context)
                values = {
                    record.name: 'res.partner,%d' % dst_partner.id,
                }
                proxy_model.write(cr,
                                  ecore.SUPERUSER_ID,
                                  model_ids,
                                  values,
                                  context=context)

    def _update_values(self, cr, uid, src_partners, dst_partner, context=None):
        _logger.debug(
            '_update_values for dst_partner: %s for src_partners: %r',
            dst_partner.id, list(map(operator.attrgetter('id'), src_partners)))

        columns = dst_partner._columns

        def write_serializer(column, item):
            if isinstance(item, browse_record):
                return item.id
            else:
                return item

        values = dict()
        for column, field in columns.iteritems():
            if field._type not in ('many2many', 'one2many') and not isinstance(
                    field, fields.function):
                for item in itertools.chain(src_partners, [dst_partner]):
                    if item[column]:
                        values[column] = write_serializer(column, item[column])

        values.pop('id', None)
        parent_id = values.pop('parent_id', None)
        dst_partner.write(values)
        if parent_id and parent_id != dst_partner.id:
            try:
                dst_partner.write({'parent_id': parent_id})
            except ValidationError:
                _logger.info(
                    'Skip recursive partner hierarchies for parent_id %s of partner: %s',
                    parent_id, dst_partner.id)

    @mute_logger('ecore.osv.expression', 'ecore.models')
    def _merge(self, cr, uid, partner_ids, dst_partner=None, context=None):
        proxy = self.pool.get('res.partner')

        partner_ids = proxy.exists(cr, uid, list(partner_ids), context=context)
        if len(partner_ids) < 2:
            return

        if len(partner_ids) > 3:
            raise UserError(
                _("For safety reasons, you cannot merge more than 3 contacts together. You can re-open the wizard several times if needed."
                  ))

        if ecore.SUPERUSER_ID != uid and len(
                set(partner.email for partner in proxy.browse(
                    cr, uid, partner_ids, context=context))) > 1:
            raise UserError(
                _("All contacts must have the same email. Only the Administrator can merge contacts with different emails."
                  ))

        if dst_partner and dst_partner.id in partner_ids:
            src_partners = proxy.browse(
                cr,
                uid, [id for id in partner_ids if id != dst_partner.id],
                context=context)
        else:
            ordered_partners = self._get_ordered_partner(
                cr, uid, partner_ids, context)
            dst_partner = ordered_partners[-1]
            src_partners = ordered_partners[:-1]
        _logger.info("dst_partner: %s", dst_partner.id)

        if ecore.SUPERUSER_ID != uid and self._model_is_installed(cr, uid, 'account.move.line', context=context) and \
                self.pool.get('account.move.line').search(cr, ecore.SUPERUSER_ID, [('partner_id', 'in', [partner.id for partner in src_partners])], context=context):
            raise UserError(
                _("Only the destination contact may be linked to existing Journal Items. Please ask the Administrator if you need to merge several contacts linked to existing Journal Items."
                  ))

        call_it = lambda function: function(
            cr, uid, src_partners, dst_partner, context=context)

        call_it(self._update_foreign_keys)
        call_it(self._update_reference_fields)
        call_it(self._update_values)

        _logger.info('(uid = %s) merged the partners %r with %s', uid,
                     list(map(operator.attrgetter('id'), src_partners)),
                     dst_partner.id)
        dst_partner.message_post(body='%s %s' %
                                 (_("Merged with the following partners:"),
                                  ", ".join('%s<%s>(ID %s)' %
                                            (p.name, p.email or 'n/a', p.id)
                                            for p in src_partners)))

        for partner in src_partners:
            partner.unlink()

    def clean_emails(self, cr, uid, context=None):
        """
        Clean the email address of the partner, if there is an email field with
        a mimum of two addresses, the system will create a new partner, with the
        information of the previous one and will copy the new cleaned email into
        the email field.
        """
        context = dict(context or {})

        proxy_model = self.pool['ir.model.fields']
        field_ids = proxy_model.search(cr,
                                       uid, [('model', '=', 'res.partner'),
                                             ('ttype', 'like', '%2many')],
                                       context=context)
        fields = proxy_model.read(cr, uid, field_ids, context=context)
        reset_fields = dict((field['name'], []) for field in fields)

        proxy_partner = self.pool['res.partner']
        context['active_test'] = False
        ids = proxy_partner.search(cr, uid, [], context=context)

        fields = ['name', 'var' 'partner_id' 'is_company', 'email']
        partners = proxy_partner.read(cr, uid, ids, fields, context=context)

        partners.sort(key=operator.itemgetter('id'))
        partners_len = len(partners)

        _logger.info('partner_len: %r', partners_len)

        for idx, partner in enumerate(partners):
            if not partner['email']:
                continue

            percent = (idx / float(partners_len)) * 100.0
            _logger.info('idx: %r', idx)
            _logger.info('percent: %r', percent)
            try:
                emails = sanitize_email(partner['email'])
                head, tail = emails[:1], emails[1:]
                email = head[0] if head else False

                proxy_partner.write(cr,
                                    uid, [partner['id']], {'email': email},
                                    context=context)

                for email in tail:
                    values = dict(reset_fields, email=email)
                    proxy_partner.copy(cr,
                                       uid,
                                       partner['id'],
                                       values,
                                       context=context)

            except Exception:
                _logger.exception("There is a problem with this partner: %r",
                                  partner)
                raise
        return True

    def close_cb(self, cr, uid, ids, context=None):
        return {'type': 'ir.actions.act_window_close'}

    def _generate_query(self, fields, maximum_group=100):
        sql_fields = []
        for field in fields:
            if field in ['email', 'name']:
                sql_fields.append('lower(%s)' % field)
            elif field in ['vat']:
                sql_fields.append("replace(%s, ' ', '')" % field)
            else:
                sql_fields.append(field)

        group_fields = ', '.join(sql_fields)

        filters = []
        for field in fields:
            if field in ['email', 'name', 'vat']:
                filters.append((field, 'IS NOT', 'NULL'))

        criteria = ' AND '.join('%s %s %s' % (field, operator, value)
                                for field, operator, value in filters)

        text = [
            "SELECT min(id), array_agg(id)",
            "FROM res_partner",
        ]

        if criteria:
            text.append('WHERE %s' % criteria)

        text.extend([
            "GROUP BY %s" % group_fields,
            "HAVING COUNT(*) >= 2",
            "ORDER BY min(id)",
        ])

        if maximum_group:
            text.extend([
                "LIMIT %s" % maximum_group,
            ])

        return ' '.join(text)

    def _compute_selected_groupby(self, this):
        group_by_str = 'group_by_'
        group_by_len = len(group_by_str)

        fields = [
            key[group_by_len:] for key in self._columns.keys()
            if key.startswith(group_by_str)
        ]

        groups = [
            field for field in fields
            if getattr(this, '%s%s' % (group_by_str, field), False)
        ]

        if not groups:
            raise UserError(
                _("You have to specify a filter for your selection"))

        return groups

    def next_cb(self, cr, uid, ids, context=None):
        """
        Don't compute any thing
        """
        context = dict(context or {}, active_test=False)
        this = self.browse(cr, uid, ids[0], context=context)
        if this.current_line_id:
            this.current_line_id.unlink()
        return self._next_screen(cr, uid, this, context)

    def _get_ordered_partner(self, cr, uid, partner_ids, context=None):
        partners = self.pool.get('res.partner').browse(cr,
                                                       uid,
                                                       list(partner_ids),
                                                       context=context)
        ordered_partners = sorted(sorted(
            partners, key=operator.attrgetter('create_date'), reverse=True),
                                  key=operator.attrgetter('active'),
                                  reverse=True)
        return ordered_partners

    def _next_screen(self, cr, uid, this, context=None):
        this.refresh()
        values = {}
        if this.line_ids:
            # in this case, we try to find the next record.
            current_line = this.line_ids[0]
            current_partner_ids = literal_eval(current_line.aggr_ids)
            values.update({
                'current_line_id':
                current_line.id,
                'partner_ids': [(6, 0, current_partner_ids)],
                'dst_partner_id':
                self._get_ordered_partner(cr, uid, current_partner_ids,
                                          context)[-1].id,
                'state':
                'selection',
            })
        else:
            values.update({
                'current_line_id': False,
                'partner_ids': [],
                'state': 'finished',
            })

        this.write(values)

        return {
            'type': 'ir.actions.act_window',
            'res_model': this._name,
            'res_id': this.id,
            'view_mode': 'form',
            'target': 'new',
        }

    def _model_is_installed(self, cr, uid, model, context=None):
        proxy = self.pool.get('ir.model')
        domain = [('model', '=', model)]
        return proxy.search_count(cr, uid, domain, context=context) > 0

    def _partner_use_in(self, cr, uid, aggr_ids, models, context=None):
        """
        Check if there is no occurence of this group of partner in the selected
        model
        """
        for model, field in models.iteritems():
            proxy = self.pool.get(model)
            domain = [(field, 'in', aggr_ids)]
            if proxy.search_count(cr, uid, domain, context=context):
                return True
        return False

    def compute_models(self, cr, uid, ids, context=None):
        """
        Compute the different models needed by the system if you want to exclude
        some partners.
        """
        assert is_integer_list(ids)

        this = self.browse(cr, uid, ids[0], context=context)

        models = {}
        if this.exclude_contact:
            models['res.users'] = 'partner_id'

        if self._model_is_installed(
                cr, uid, 'account.move.line',
                context=context) and this.exclude_journal_item:
            models['account.move.line'] = 'partner_id'

        return models

    def _process_query(self, cr, uid, ids, query, context=None):
        """
        Execute the select request and write the result in this wizard
        """
        proxy = self.pool.get('base.partner.merge.line')
        this = self.browse(cr, uid, ids[0], context=context)
        models = self.compute_models(cr, uid, ids, context=context)
        cr.execute(query)

        counter = 0
        for min_id, aggr_ids in cr.fetchall():
            if models and self._partner_use_in(
                    cr, uid, aggr_ids, models, context=context):
                continue
            values = {
                'wizard_id': this.id,
                'min_id': min_id,
                'aggr_ids': aggr_ids,
            }

            proxy.create(cr, uid, values, context=context)
            counter += 1

        values = {
            'state': 'selection',
            'number_group': counter,
        }

        this.write(values)

        _logger.info("counter: %s", counter)

    def start_process_cb(self, cr, uid, ids, context=None):
        """
        Start the process.
        * Compute the selected groups (with duplication)
        * If the user has selected the 'exclude_XXX' fields, avoid the partners.
        """
        assert is_integer_list(ids)

        context = dict(context or {}, active_test=False)
        this = self.browse(cr, uid, ids[0], context=context)
        groups = self._compute_selected_groupby(this)
        query = self._generate_query(groups, this.maximum_group)
        self._process_query(cr, uid, ids, query, context=context)

        return self._next_screen(cr, uid, this, context)

    def automatic_process_cb(self, cr, uid, ids, context=None):
        assert is_integer_list(ids)
        this = self.browse(cr, uid, ids[0], context=context)
        this.start_process_cb()
        this.refresh()

        for line in this.line_ids:
            partner_ids = literal_eval(line.aggr_ids)
            self._merge(cr, uid, partner_ids, context=context)
            line.unlink()
            cr.commit()

        this.write({'state': 'finished'})
        return {
            'type': 'ir.actions.act_window',
            'res_model': this._name,
            'res_id': this.id,
            'view_mode': 'form',
            'target': 'new',
        }

    def parent_migration_process_cb(self, cr, uid, ids, context=None):
        assert is_integer_list(ids)

        context = dict(context or {}, active_test=False)
        this = self.browse(cr, uid, ids[0], context=context)

        query = """
            SELECT
                min(p1.id),
                array_agg(DISTINCT p1.id)
            FROM
                res_partner as p1
            INNER join
                res_partner as p2
            ON
                p1.email = p2.email AND
                p1.name = p2.name AND
                (p1.parent_id = p2.id OR p1.id = p2.parent_id)
            WHERE
                p2.id IS NOT NULL
            GROUP BY
                p1.email,
                p1.name,
                CASE WHEN p1.parent_id = p2.id THEN p2.id
                    ELSE p1.id
                END
            HAVING COUNT(*) >= 2
            ORDER BY
                min(p1.id)
        """

        self._process_query(cr, uid, ids, query, context=context)

        for line in this.line_ids:
            partner_ids = literal_eval(line.aggr_ids)
            self._merge(cr, uid, partner_ids, context=context)
            line.unlink()
            cr.commit()

        this.write({'state': 'finished'})

        cr.execute("""
            UPDATE
                res_partner
            SET
                is_company = NULL,
                parent_id = NULL
            WHERE
                parent_id = id
        """)

        return {
            'type': 'ir.actions.act_window',
            'res_model': this._name,
            'res_id': this.id,
            'view_mode': 'form',
            'target': 'new',
        }

    def update_all_process_cb(self, cr, uid, ids, context=None):
        assert is_integer_list(ids)

        # WITH RECURSIVE cycle(id, parent_id) AS (
        #     SELECT id, parent_id FROM res_partner
        #   UNION
        #     SELECT  cycle.id, res_partner.parent_id
        #     FROM    res_partner, cycle
        #     WHERE   res_partner.id = cycle.parent_id AND
        #             cycle.id != cycle.parent_id
        # )
        # UPDATE  res_partner
        # SET     parent_id = NULL
        # WHERE   id in (SELECT id FROM cycle WHERE id = parent_id);

        this = self.browse(cr, uid, ids[0], context=context)

        self.parent_migration_process_cb(cr, uid, ids, context=None)

        list_merge = [
            {
                'group_by_vat': True,
                'group_by_email': True,
                'group_by_name': True
            },
            # {'group_by_name': True, 'group_by_is_company': True, 'group_by_parent_id': True},
            # {'group_by_email': True, 'group_by_is_company': True, 'group_by_parent_id': True},
            # {'group_by_name': True, 'group_by_vat': True, 'group_by_is_company': True, 'exclude_journal_item': True},
            # {'group_by_email': True, 'group_by_vat': True, 'group_by_is_company': True, 'exclude_journal_item': True},
            # {'group_by_email': True, 'group_by_is_company': True, 'exclude_contact': True, 'exclude_journal_item': True},
            # {'group_by_name': True, 'group_by_is_company': True, 'exclude_contact': True, 'exclude_journal_item': True}
        ]

        for merge_value in list_merge:
            id = self.create(cr, uid, merge_value, context=context)
            self.automatic_process_cb(cr, uid, [id], context=context)

        cr.execute("""
            UPDATE
                res_partner
            SET
                is_company = NULL
            WHERE
                parent_id IS NOT NULL AND
                is_company IS NOT NULL
        """)

        # cr.execute("""
        #     UPDATE
        #         res_partner as p1
        #     SET
        #         is_company = NULL,
        #         parent_id = (
        #             SELECT  p2.id
        #             FROM    res_partner as p2
        #             WHERE   p2.email = p1.email AND
        #                     p2.parent_id != p2.id
        #             LIMIT 1
        #         )
        #     WHERE
        #         p1.parent_id = p1.id
        # """)

        return self._next_screen(cr, uid, this, context)

    def merge_cb(self, cr, uid, ids, context=None):
        assert is_integer_list(ids)

        context = dict(context or {}, active_test=False)
        this = self.browse(cr, uid, ids[0], context=context)

        partner_ids = set(map(int, this.partner_ids))
        if not partner_ids:
            this.write({'state': 'finished'})
            return {
                'type': 'ir.actions.act_window',
                'res_model': this._name,
                'res_id': this.id,
                'view_mode': 'form',
                'target': 'new',
            }

        self._merge(cr, uid, partner_ids, this.dst_partner_id, context=context)

        if this.current_line_id:
            this.current_line_id.unlink()

        return self._next_screen(cr, uid, this, context)

    def auto_set_parent_id(self, cr, uid, ids, context=None):
        assert is_integer_list(ids)

        # select partner who have one least invoice
        partner_treated = ['@gmail.com']
        cr.execute("""  SELECT p.id, p.email
                        FROM res_partner as p 
                        LEFT JOIN account_invoice as a 
                        ON p.id = a.partner_id AND a.state in ('open','paid')
                        WHERE p.grade_id is NOT NULL
                        GROUP BY p.id
                        ORDER BY COUNT(a.id) DESC
                """)
        re_email = re.compile(r".*@")
        for id, email in cr.fetchall():
            # check email domain
            email = re_email.sub("@", email or "")
            if not email or email in partner_treated:
                continue
            partner_treated.append(email)

            # don't update the partners if they are more of one who have invoice
            cr.execute(
                """  SELECT *
                            FROM res_partner as p
                            WHERE p.id != %s AND p.email LIKE %s AND
                                EXISTS (SELECT * FROM account_invoice as a WHERE p.id = a.partner_id AND a.state in ('open','paid'))
                    """, (id, '%' + email))

            if len(cr.fetchall()) > 1:
                _logger.info("%s MORE OF ONE COMPANY", email)
                continue

            # to display changed values
            cr.execute(
                """  SELECT id,email
                            FROM res_partner
                            WHERE parent_id != %s AND id != %s AND email LIKE %s
                    """, (id, id, '%' + email))
            _logger.info("%r", cr.fetchall())

            # upgrade
            cr.execute(
                """  UPDATE res_partner
                            SET parent_id = %s
                            WHERE id != %s AND email LIKE %s
                    """, (id, id, '%' + email))
        return False
Beispiel #13
0
class website_menu(osv.osv):
    _name = "website.menu"
    _description = "Website Menu"
    _columns = {
        'name':
        fields.char('Menu', required=True, translate=True),
        'url':
        fields.char('Url'),
        'new_window':
        fields.boolean('New Window'),
        'sequence':
        fields.integer('Sequence'),
        # TODO: support multiwebsite once done for ir.ui.views
        'website_id':
        fields.many2one('website', 'Website'),
        'parent_id':
        fields.many2one('website.menu',
                        'Parent Menu',
                        select=True,
                        ondelete="cascade"),
        'child_id':
        fields.one2many('website.menu', 'parent_id', string='Child Menus'),
        'parent_left':
        fields.integer('Parent Left', select=True),
        'parent_right':
        fields.integer('Parent Right', select=True),
    }

    def __defaults_sequence(self, cr, uid, context):
        menu = self.search_read(cr,
                                uid, [(1, "=", 1)], ["sequence"],
                                limit=1,
                                order="sequence DESC",
                                context=context)
        return menu and menu[0]["sequence"] or 0

    _defaults = {
        'url': '',
        'sequence': __defaults_sequence,
        'new_window': False,
    }
    _parent_store = True
    _parent_order = 'sequence'
    _order = "sequence"

    # would be better to take a menu_id as argument
    def get_tree(self, cr, uid, website_id, menu_id=None, context=None):
        def make_tree(node):
            menu_node = dict(
                id=node.id,
                name=node.name,
                url=node.url,
                new_window=node.new_window,
                sequence=node.sequence,
                parent_id=node.parent_id.id,
                children=[],
            )
            for child in node.child_id:
                menu_node['children'].append(make_tree(child))
            return menu_node

        if menu_id:
            menu = self.browse(cr, uid, menu_id, context=context)
        else:
            menu = self.pool.get('website').browse(cr,
                                                   uid,
                                                   website_id,
                                                   context=context).menu_id
        return make_tree(menu)

    def save(self, cr, uid, website_id, data, context=None):
        def replace_id(old_id, new_id):
            for menu in data['data']:
                if menu['id'] == old_id:
                    menu['id'] = new_id
                if menu['parent_id'] == old_id:
                    menu['parent_id'] = new_id

        to_delete = data['to_delete']
        if to_delete:
            self.unlink(cr, uid, to_delete, context=context)
        for menu in data['data']:
            mid = menu['id']
            if isinstance(mid, basestring):
                new_id = self.create(cr,
                                     uid, {'name': menu['name']},
                                     context=context)
                replace_id(mid, new_id)
        for menu in data['data']:
            self.write(cr, uid, [menu['id']], menu, context=context)
        return True
Beispiel #14
0
class website(osv.osv):
    def _get_menu(self, cr, uid, ids, name, arg, context=None):
        res = {}
        menu_obj = self.pool.get('website.menu')
        for id in ids:
            menu_ids = menu_obj.search(cr,
                                       uid, [('parent_id', '=', False),
                                             ('website_id', '=', id)],
                                       order='id',
                                       context=context)
            res[id] = menu_ids and menu_ids[0] or False
        return res

    _name = "website"  # Avoid website.website convention for conciseness (for new api). Got a special authorization from xmo and rco
    _description = "Website"
    _columns = {
        'name':
        fields.char('Website Name'),
        'domain':
        fields.char('Website Domain'),
        'company_id':
        fields.many2one('res.company', string="Company"),
        'language_ids':
        fields.many2many('res.lang', 'website_lang_rel', 'website_id',
                         'lang_id', 'Languages'),
        'default_lang_id':
        fields.many2one('res.lang', string="Default language"),
        'default_lang_code':
        fields.related('default_lang_id',
                       'code',
                       type="char",
                       string="Default language code",
                       store=True),
        'social_twitter':
        fields.char('Twitter Account'),
        'social_facebook':
        fields.char('Facebook Account'),
        'social_github':
        fields.char('GitHub Account'),
        'social_linkedin':
        fields.char('LinkedIn Account'),
        'social_youtube':
        fields.char('Youtube Account'),
        'social_googleplus':
        fields.char('Google+ Account'),
        'google_analytics_key':
        fields.char('Google Analytics Key'),
        'user_id':
        fields.many2one('res.users', string='Public User'),
        'compress_html':
        fields.boolean('Compress HTML'),
        'cdn_activated':
        fields.boolean('Activate CDN for assets'),
        'cdn_url':
        fields.char('CDN Base URL'),
        'cdn_filters':
        fields.text(
            'CDN Filters',
            help=
            "URL matching those filters will be rewritten using the CDN Base URL"
        ),
        'partner_id':
        fields.related('user_id',
                       'partner_id',
                       type='many2one',
                       relation='res.partner',
                       string='Public Partner'),
        'menu_id':
        fields.function(_get_menu,
                        relation='website.menu',
                        type='many2one',
                        string='Main Menu')
    }
    _defaults = {
        'user_id':
        lambda self, cr, uid, c: self.pool['ir.model.data'].xmlid_to_res_id(
            cr, ecore.SUPERUSER_ID, 'base.public_user'),
        'company_id':
        lambda self, cr, uid, c: self.pool['ir.model.data'].xmlid_to_res_id(
            cr, ecore.SUPERUSER_ID, 'base.main_company'),
        'compress_html':
        False,
        'cdn_activated':
        False,
        'cdn_url':
        '',
        'cdn_filters':
        '\n'.join(DEFAULT_CDN_FILTERS),
    }

    # cf. Wizard hack in website_views.xml
    def noop(self, *args, **kwargs):
        pass

    def write(self, cr, uid, ids, vals, context=None):
        self._get_languages.clear_cache(self)
        return super(website, self).write(cr, uid, ids, vals, context)

    def new_page(self,
                 cr,
                 uid,
                 name,
                 template='website.default_page',
                 ispage=True,
                 context=None):
        context = context or {}
        imd = self.pool.get('ir.model.data')
        view = self.pool.get('ir.ui.view')
        template_module, template_name = template.split('.')

        # completely arbitrary max_length
        page_name = slugify(name, max_length=50)
        page_xmlid = "%s.%s" % (template_module, page_name)

        # find a free xmlid
        inc = 0
        dom = [('website_id', '=', False),
               ('website_id', '=', context.get('website_id'))]
        while view.search(cr,
                          ecore.SUPERUSER_ID,
                          [('key', '=', page_xmlid), '|'] + dom,
                          context=dict(context or {}, active_test=False)):
            inc += 1
            page_xmlid = "%s.%s" % (template_module, page_name +
                                    (inc and "-%s" % inc or ""))
        page_name += (inc and "-%s" % inc or "")

        # new page
        _, template_id = imd.get_object_reference(cr, uid, template_module,
                                                  template_name)
        website_id = context.get('website_id')
        key = template_module + '.' + page_name
        page_id = view.copy(cr,
                            uid,
                            template_id, {
                                'website_id': website_id,
                                'key': key
                            },
                            context=context)
        page = view.browse(cr, uid, page_id, context=dict(context, lang=None))
        page.write({
            'arch': page.arch.replace(template, page_xmlid),
            'name': page_name,
            'page': ispage,
        })
        return page_xmlid

    def key_to_view_id(self, cr, uid, view_id, context=None):
        View = self.pool.get('ir.ui.view')
        return View.search(cr,
                           uid,
                           [('id', '=', view_id), "|",
                            ('website_id', '=', context.get('website_id')),
                            ('website_id', '=', False), ('page', '=', True),
                            ('type', '=', 'qweb')],
                           context=context)

    def delete_page(self, cr, uid, view_id, context=None):
        if context is None:
            context = {}
        View = self.pool.get('ir.ui.view')
        view_find = self.key_to_view_id(cr, uid, view_id, context=context)
        if view_find:
            View.unlink(cr, uid, view_find, context=context)

    def rename_page(self, cr, uid, view_id, new_name, context=None):
        if context is None:
            context = {}
        View = self.pool.get('ir.ui.view')
        view_find = self.key_to_view_id(cr, uid, view_id, context=context)
        if view_find:
            v = View.browse(cr, uid, view_find, context=context)

            new_name = slugify(new_name, max_length=50)
            # Prefix by module if not already done by end user
            prefix = v.key.split('.')[0]
            if not new_name.startswith(prefix):
                new_name = "%s.%s" % (prefix, new_name)

            View.write(cr, uid, view_find, {
                'key': new_name,
                'arch_db': v.arch_db.replace(v.key, new_name, 1)
            })
            return new_name

    def page_search_dependencies(self, cr, uid, view_id=False, context=None):
        dep = {}
        if not view_id:
            return dep

        # search dependencies just for information.
        # It will not catch 100% of dependencies and False positive is more than possible
        # Each module could add dependences in this dict
        if context is None:
            context = {}
        View = self.pool.get('ir.ui.view')
        Menu = self.pool.get('website.menu')

        view = View.browse(cr, uid, view_id, context=context)
        website_id = context.get('website_id')
        name = view.key.replace("website.", "")
        fullname = "website.%s" % name

        if view.page:
            # search for page with link
            page_search_dom = [
                '|', ('website_id', '=', website_id),
                ('website_id', '=', False), '|',
                ('arch_db', 'ilike', '/page/%s' % name),
                ('arch_db', 'ilike', '/page/%s' % fullname)
            ]
            pages = View.search(cr, uid, page_search_dom, context=context)
            if pages:
                page_key = _('Page')
                dep[page_key] = []
            for page in View.browse(cr, uid, pages, context=context):
                if page.page:
                    dep[page_key].append({
                        'text':
                        _('Page <b>%s</b> seems to have a link to this page !')
                        % page.key,
                        'link':
                        '/page/%s' % page.key
                    })
                else:
                    dep[page_key].append({
                        'text':
                        _('Template <b>%s (id:%s)</b> seems to have a link to this page !'
                          ) % (page.key, page.id),
                        'link':
                        '#'
                    })

            # search for menu with link
            menu_search_dom = [
                '|', ('website_id', '=', website_id),
                ('website_id', '=', False), '|',
                ('url', 'ilike', '/page/%s' % name),
                ('url', 'ilike', '/page/%s' % fullname)
            ]

            menus = Menu.search(cr, uid, menu_search_dom, context=context)
            if menus:
                menu_key = _('Menu')
                dep[menu_key] = []
            for menu in Menu.browse(cr, uid, menus, context=context):
                dep[menu_key].append({
                    'text':
                    _('Menu <b>%s</b> seems to have a link to this page !') %
                    menu.name,
                    'link':
                    False
                })

        return dep

    def page_for_name(self,
                      cr,
                      uid,
                      ids,
                      name,
                      module='website',
                      context=None):
        # whatever
        return '%s.%s' % (module, slugify(name, max_length=50))

    def page_exists(self, cr, uid, ids, name, module='website', context=None):
        try:
            name = (name or "").replace("/page/website.",
                                        "").replace("/page/", "")
            if not name:
                return False
            return self.pool["ir.model.data"].get_object_reference(
                cr, uid, module, name)
        except:
            return False

    @ecore.tools.ormcache('id')
    def _get_languages(self, cr, uid, id, context=None):
        website = self.browse(cr, uid, id)
        return [(lg.code, lg.name) for lg in website.language_ids]

    def get_cdn_url(self, cr, uid, uri, context=None):
        # Currently only usable in a website_enable request context
        if request and request.website and not request.debug and request.website.user_id.id == request.uid:
            cdn_url = request.website.cdn_url
            cdn_filters = (request.website.cdn_filters or '').splitlines()
            for flt in cdn_filters:
                if flt and re.match(flt, uri):
                    return urlparse.urljoin(cdn_url, uri)
        return uri

    def get_languages(self, cr, uid, ids, context=None):
        return self._get_languages(cr, uid, ids[0])

    def get_alternate_languages(self, cr, uid, ids, req=None, context=None):
        langs = []
        if req is None:
            req = request.httprequest
        default = self.get_current_website(cr, uid,
                                           context=context).default_lang_code
        shorts = []

        def get_url_localized(router, lang):
            arguments = dict(request.endpoint_arguments)
            for k, v in arguments.items():
                if isinstance(v, orm.browse_record):
                    arguments[k] = v.with_context(lang=lang)
            return router.build(request.endpoint, arguments)

        router = request.httprequest.app.get_db_router(request.db).bind('')
        for code, name in self.get_languages(cr, uid, ids, context=context):
            lg_path = ('/' + code) if code != default else ''
            lg = code.split('_')
            shorts.append(lg[0])
            uri = request.endpoint and get_url_localized(
                router, code) or request.httprequest.path
            if req.query_string:
                uri += '?' + req.query_string
            lang = {
                'hreflang': ('-'.join(lg)).lower(),
                'short': lg[0],
                'href': req.url_root[0:-1] + lg_path + uri,
            }
            langs.append(lang)
        for lang in langs:
            if shorts.count(lang['short']) == 1:
                lang['hreflang'] = lang['short']
        return langs

    @ecore.tools.ormcache('domain_name')
    def _get_current_website_id(self, cr, uid, domain_name, context=None):
        ids = self.search(cr,
                          uid, [('domain', '=', domain_name)],
                          limit=1,
                          context=context)
        return ids and ids[0] or self.search(cr, uid, [], limit=1)[0]

    def get_current_website(self, cr, uid, context=None):
        domain_name = request.httprequest.environ.get('HTTP_HOST',
                                                      '').split(':')[0]
        website_id = self._get_current_website_id(cr, uid, domain_name)
        request.context['website_id'] = website_id
        return self.browse(cr, uid, website_id, context=context)

    def is_publisher(self, cr, uid, ids, context=None):
        Access = self.pool['ir.model.access']
        is_website_publisher = Access.check(cr,
                                            uid,
                                            'ir.ui.view',
                                            'write',
                                            False,
                                            context=context)
        return is_website_publisher

    def is_user(self, cr, uid, ids, context=None):
        Access = self.pool['ir.model.access']
        return Access.check(cr,
                            uid,
                            'ir.ui.menu',
                            'read',
                            False,
                            context=context)

    def get_template(self, cr, uid, ids, template, context=None):
        View = self.pool['ir.ui.view']
        if isinstance(template, (int, long)):
            view_id = template
        else:
            if '.' not in template:
                template = 'website.%s' % template
            view_id = View.get_view_id(cr, uid, template, context=context)
        if not view_id:
            raise NotFound
        return View.browse(cr, uid, view_id, context=context)

    def _render(self, cr, uid, ids, template, values=None, context=None):
        # TODO: remove this. (just kept for backward api compatibility for saas-3)
        return self.pool['ir.ui.view'].render(cr,
                                              uid,
                                              template,
                                              values=values,
                                              context=context)

    def render(self,
               cr,
               uid,
               ids,
               template,
               values=None,
               status_code=None,
               context=None):
        # TODO: remove this. (just kept for backward api compatibility for saas-3)
        return request.render(template, values, uid=uid)

    def pager(self,
              cr,
              uid,
              ids,
              url,
              total,
              page=1,
              step=30,
              scope=5,
              url_args=None,
              context=None):
        # Compute Pager
        page_count = int(math.ceil(float(total) / step))

        page = max(1, min(int(page if str(page).isdigit() else 1), page_count))
        scope -= 1

        pmin = max(page - int(math.floor(scope / 2)), 1)
        pmax = min(pmin + scope, page_count)

        if pmax - pmin < scope:
            pmin = pmax - scope if pmax - scope > 0 else 1

        def get_url(page):
            _url = "%s/page/%s" % (url, page) if page > 1 else url
            if url_args:
                _url = "%s?%s" % (_url, werkzeug.url_encode(url_args))
            return _url

        return {
            "page_count":
            page_count,
            "offset": (page - 1) * step,
            "page": {
                'url': get_url(page),
                'num': page
            },
            "page_start": {
                'url': get_url(pmin),
                'num': pmin
            },
            "page_previous": {
                'url': get_url(max(pmin, page - 1)),
                'num': max(pmin, page - 1)
            },
            "page_next": {
                'url': get_url(min(pmax, page + 1)),
                'num': min(pmax, page + 1)
            },
            "page_end": {
                'url': get_url(pmax),
                'num': pmax
            },
            "pages": [{
                'url': get_url(page),
                'num': page
            } for page in xrange(pmin, pmax + 1)]
        }

    def rule_is_enumerable(self, rule):
        """ Checks that it is possible to generate sensible GET queries for
        a given rule (if the endpoint matches its own requirements)

        :type rule: werkzeug.routing.Rule
        :rtype: bool
        """
        endpoint = rule.endpoint
        methods = endpoint.routing.get('methods') or ['GET']

        converters = rule._converters.values()
        if not ('GET' in methods and endpoint.routing['type'] == 'http'
                and endpoint.routing['auth'] in ('none', 'public')
                and endpoint.routing.get('website', False) and all(
                    hasattr(converter, 'generate') for converter in converters)
                and endpoint.routing.get('website')):
            return False

        # dont't list routes without argument having no default value or converter
        spec = inspect.getargspec(endpoint.method.original_func)

        # remove self and arguments having a default value
        defaults_count = len(spec.defaults or [])
        args = spec.args[1:(-defaults_count or None)]

        # check that all args have a converter
        return all((arg in rule._converters) for arg in args)

    def enumerate_pages(self, cr, uid, ids, query_string=None, context=None):
        """ Available pages in the website/CMS. This is mostly used for links
        generation and can be overridden by modules setting up new HTML
        controllers for dynamic pages (e.g. blog).

        By default, returns template views marked as pages.

        :param str query_string: a (user-provided) string, fetches pages
                                 matching the string
        :returns: a list of mappings with two keys: ``name`` is the displayable
                  name of the resource (page), ``url`` is the absolute URL
                  of the same.
        :rtype: list({name: str, url: str})
        """
        router = request.httprequest.app.get_db_router(request.db)
        # Force enumeration to be performed as public user
        url_set = set()
        for rule in router.iter_rules():
            if not self.rule_is_enumerable(rule):
                continue

            converters = rule._converters or {}
            if query_string and not converters and (
                    query_string not in rule.build([{}],
                                                   append_unknown=False)[1]):
                continue
            values = [{}]
            convitems = converters.items()
            # converters with a domain are processed after the other ones
            gd = lambda x: hasattr(x[1], 'domain') and (x[1].domain <> '[]')
            convitems.sort(lambda x, y: cmp(gd(x), gd(y)))
            for (i, (name, converter)) in enumerate(convitems):
                newval = []
                for val in values:
                    query = i == (len(convitems) - 1) and query_string
                    for v in converter.generate(request.cr,
                                                uid,
                                                query=query,
                                                args=val,
                                                context=context):
                        newval.append(val.copy())
                        v[name] = v['loc']
                        del v['loc']
                        newval[-1].update(v)
                values = newval

            for value in values:
                domain_part, url = rule.build(value, append_unknown=False)
                page = {'loc': url}
                for key, val in value.items():
                    if key.startswith('__'):
                        page[key[2:]] = val
                if url in ('/sitemap.xml', ):
                    continue
                if url in url_set:
                    continue
                url_set.add(url)

                yield page

    def search_pages(self,
                     cr,
                     uid,
                     ids,
                     needle=None,
                     limit=None,
                     context=None):
        name = re.sub(r"^/p(a(g(e(/(w(e(b(s(i(t(e(\.)?)?)?)?)?)?)?)?)?)?)?)?",
                      "", needle or "")
        res = []
        for page in self.enumerate_pages(cr,
                                         uid,
                                         ids,
                                         query_string=name,
                                         context=context):
            if needle in page['loc']:
                res.append(page)
                if len(res) == limit:
                    break
        return res

    def image_url(self, cr, uid, record, field, size=None, context=None):
        """Returns a local url that points to the image field of a given browse record."""
        sudo_record = record.sudo()
        sha = hashlib.sha1(getattr(sudo_record,
                                   '__last_update')).hexdigest()[0:7]
        size = '' if size is None else '/%s' % size
        return '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id,
                                                    field, size, sha)
Beispiel #15
0
class module(osv.osv):
    _name = "ir.module.module"
    _rec_name = "shortdesc"
    _description = "Module"

    def fields_view_get(self,
                        cr,
                        uid,
                        view_id=None,
                        view_type='form',
                        context=None,
                        toolbar=False,
                        submenu=False):
        res = super(module, self).fields_view_get(cr,
                                                  uid,
                                                  view_id=view_id,
                                                  view_type=view_type,
                                                  context=context,
                                                  toolbar=toolbar,
                                                  submenu=False)
        result = self.pool.get('ir.model.data').get_object_reference(
            cr, uid, 'base', 'action_server_module_immediate_install')[1]
        if view_type == 'form':
            if res.get('toolbar', False):
                list = [
                    rec for rec in res['toolbar']['action']
                    if rec.get('id', False) != result
                ]
                res['toolbar'] = {'action': list}
        return res

    @classmethod
    def get_module_info(cls, name):
        info = {}
        try:
            info = modules.load_information_from_description_file(name)
        except Exception:
            _logger.debug(
                'Error when trying to fetch informations for '
                'module %s',
                name,
                exc_info=True)
        return info

    def _get_desc(self, cr, uid, ids, field_name=None, arg=None, context=None):
        res = dict.fromkeys(ids, '')
        for module in self.browse(cr, uid, ids, context=context):
            path = get_module_resource(module.name,
                                       'static/description/index.html')
            if path:
                with tools.file_open(path, 'rb') as desc_file:
                    doc = desc_file.read()
                    html = lxml.html.document_fromstring(doc)
                    for element, attribute, link, pos in html.iterlinks():
                        if element.get('src') and not '//' in element.get(
                                'src') and not 'static/' in element.get('src'):
                            element.set(
                                'src', "/%s/static/description/%s" %
                                (module.name, element.get('src')))
                    res[module.id] = html_sanitize(lxml.html.tostring(html))
            else:
                overrides = {
                    'embed_stylesheet': False,
                    'doctitle_xform': False,
                    'output_encoding': 'unicode',
                    'xml_declaration': False,
                }
                output = publish_string(source=module.description or '',
                                        settings_overrides=overrides,
                                        writer=MyWriter())
                res[module.id] = html_sanitize(output)
        return res

    def _get_latest_version(self,
                            cr,
                            uid,
                            ids,
                            field_name=None,
                            arg=None,
                            context=None):
        default_version = modules.adapt_version('1.0')
        res = dict.fromkeys(ids, default_version)
        for m in self.browse(cr, uid, ids):
            res[m.id] = self.get_module_info(m.name).get(
                'version', default_version)
        return res

    def _get_views(self,
                   cr,
                   uid,
                   ids,
                   field_name=None,
                   arg=None,
                   context=None):
        res = {}
        model_data_obj = self.pool.get('ir.model.data')

        dmodels = []
        if field_name is None or 'views_by_module' in field_name:
            dmodels.append('ir.ui.view')
        if field_name is None or 'reports_by_module' in field_name:
            dmodels.append('ir.actions.report.xml')
        if field_name is None or 'menus_by_module' in field_name:
            dmodels.append('ir.ui.menu')
        assert dmodels, "no models for %s" % field_name

        for module_rec in self.browse(cr, uid, ids, context=context):
            res_mod_dic = res[module_rec.id] = {
                'menus_by_module': [],
                'reports_by_module': [],
                'views_by_module': []
            }

            # Skip uninstalled modules below, no data to find anyway.
            if module_rec.state not in ('installed', 'to upgrade',
                                        'to remove'):
                continue

            # then, search and group ir.model.data records
            imd_models = dict([(m, []) for m in dmodels])
            imd_ids = model_data_obj.search(cr, uid,
                                            [('module', '=', module_rec.name),
                                             ('model', 'in', tuple(dmodels))])

            for imd_res in model_data_obj.read(cr,
                                               uid,
                                               imd_ids, ['model', 'res_id'],
                                               context=context):
                imd_models[imd_res['model']].append(imd_res['res_id'])

            def browse(model):
                M = self.pool[model]
                # as this method is called before the module update, some xmlid may be invalid at this stage
                # explictly filter records before reading them
                ids = M.exists(cr, uid, imd_models.get(model, []), context)
                return M.browse(cr, uid, ids, context)

            def format_view(v):
                aa = v.inherit_id and '* INHERIT ' or ''
                return '%s%s (%s)' % (aa, v.name, v.type)

            res_mod_dic['views_by_module'] = map(format_view,
                                                 browse('ir.ui.view'))
            res_mod_dic['reports_by_module'] = map(
                attrgetter('name'), browse('ir.actions.report.xml'))
            res_mod_dic['menus_by_module'] = map(attrgetter('complete_name'),
                                                 browse('ir.ui.menu'))

        for key in res.iterkeys():
            for k, v in res[key].iteritems():
                res[key][k] = "\n".join(sorted(v))
        return res

    def _get_icon_image(self,
                        cr,
                        uid,
                        ids,
                        field_name=None,
                        arg=None,
                        context=None):
        res = dict.fromkeys(ids, '')
        for module in self.browse(cr, uid, ids, context=context):
            path = get_module_resource(module.name, 'static', 'description',
                                       'icon.png')
            if path:
                image_file = tools.file_open(path, 'rb')
                try:
                    res[module.id] = image_file.read().encode('base64')
                finally:
                    image_file.close()
        return res

    _columns = {
        'name':
        fields.char("Technical Name",
                    readonly=True,
                    required=True,
                    select=True),
        'category_id':
        fields.many2one('ir.module.category',
                        'Category',
                        readonly=True,
                        select=True),
        'shortdesc':
        fields.char('Module Name', readonly=True, translate=True),
        'summary':
        fields.char('Summary', readonly=True, translate=True),
        'description':
        fields.text("Description", readonly=True, translate=True),
        'description_html':
        fields.function(_get_desc,
                        string='Description HTML',
                        type='html',
                        method=True,
                        readonly=True),
        'author':
        fields.char("Author", readonly=True),
        'maintainer':
        fields.char('Maintainer', readonly=True),
        'contributors':
        fields.text('Contributors', readonly=True),
        'website':
        fields.char("Website", readonly=True),

        # attention: Incorrect field names !!
        #   installed_version refers the latest version (the one on disk)
        #   latest_version refers the installed version (the one in database)
        #   published_version refers the version available on the repository
        'installed_version':
        fields.function(_get_latest_version,
                        string='Latest Version',
                        type='char'),
        'latest_version':
        fields.char('Installed Version', readonly=True),
        'published_version':
        fields.char('Published Version', readonly=True),
        'url':
        fields.char('URL', readonly=True),
        'sequence':
        fields.integer('Sequence'),
        'dependencies_id':
        fields.one2many('ir.module.module.dependency',
                        'module_id',
                        'Dependencies',
                        readonly=True),
        'auto_install':
        fields.boolean(
            'Automatic Installation',
            help='An auto-installable module is automatically installed by the '
            'system when all its dependencies are satisfied. '
            'If the module has no dependency, it is always installed.'),
        'state':
        fields.selection([('uninstallable', 'Not Installable'),
                          ('uninstalled', 'Not Installed'),
                          ('installed', 'Installed'),
                          ('to upgrade', 'To be upgraded'),
                          ('to remove', 'To be removed'),
                          ('to install', 'To be installed')],
                         string='Status',
                         readonly=True,
                         select=True),
        'demo':
        fields.boolean('Demo Data', readonly=True),
        'license':
        fields.selection(
            [('GPL-2', 'GPL Version 2'),
             ('GPL-2 or any later version', 'GPL-2 or later version'),
             ('GPL-3', 'GPL Version 3'),
             ('GPL-3 or any later version', 'GPL-3 or later version'),
             ('AGPL-3', 'Affero GPL-3'), ('LGPL-3', 'LGPL Version 3'),
             ('Other OSI approved licence', 'Other OSI Approved Licence'),
             ('OEEL-1', 'eCore Enterprise Edition License v1.0'),
             ('Other proprietary', 'Other Proprietary')],
            string='License',
            readonly=True),
        'menus_by_module':
        fields.function(_get_views,
                        string='Menus',
                        type='text',
                        multi="meta",
                        store=True),
        'reports_by_module':
        fields.function(_get_views,
                        string='Reports',
                        type='text',
                        multi="meta",
                        store=True),
        'views_by_module':
        fields.function(_get_views,
                        string='Views',
                        type='text',
                        multi="meta",
                        store=True),
        'application':
        fields.boolean('Application', readonly=True),
        'icon':
        fields.char('Icon URL'),
        'icon_image':
        fields.function(_get_icon_image, string='Icon', type="binary"),
    }

    _defaults = {
        'state': 'uninstalled',
        'sequence': 100,
        'demo': False,
        'license': 'LGPL-3',
    }
    _order = 'sequence,name'

    def _name_uniq_msg(self, cr, uid, ids, context=None):
        return _('The name of the module must be unique !')

    _sql_constraints = [
        ('name_uniq', 'UNIQUE (name)', _name_uniq_msg),
    ]

    def unlink(self, cr, uid, ids, context=None):
        if not ids:
            return True
        if isinstance(ids, (int, long)):
            ids = [ids]
        mod_names = []
        for mod in self.read(cr, uid, ids, ['state', 'name'], context):
            if mod['state'] in ('installed', 'to upgrade', 'to remove',
                                'to install'):
                raise UserError(
                    _('You try to remove a module that is installed or will be installed'
                      ))
            mod_names.append(mod['name'])
        #Removing the entry from ir_model_data
        #ids_meta = self.pool.get('ir.model.data').search(cr, uid, [('name', '=', 'module_meta_information'), ('module', 'in', mod_names)])

        #if ids_meta:
        #    self.pool.get('ir.model.data').unlink(cr, uid, ids_meta, context)

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

    @staticmethod
    def _check_external_dependencies(terp):
        depends = terp.get('external_dependencies')
        if not depends:
            return
        for pydep in depends.get('python', []):
            try:
                importlib.import_module(pydep)
            except ImportError:
                raise ImportError('No module named %s' % (pydep, ))

        for binary in depends.get('bin', []):
            try:
                tools.find_in_path(binary)
            except IOError:
                raise Exception('Unable to find %r in path' % (binary, ))

    @classmethod
    def check_external_dependencies(cls, module_name, newstate='to install'):
        terp = cls.get_module_info(module_name)
        try:
            cls._check_external_dependencies(terp)
        except Exception, e:
            if newstate == 'to install':
                msg = _(
                    'Unable to install module "%s" because an external dependency is not met: %s'
                )
            elif newstate == 'to upgrade':
                msg = _(
                    'Unable to upgrade module "%s" because an external dependency is not met: %s'
                )
            else:
                msg = _(
                    'Unable to process module "%s" because an external dependency is not met: %s'
                )
            raise UserError(msg % (module_name, e.args[0]))
Beispiel #16
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 #17
0
class lang(osv.osv):
    _name = "res.lang"
    _description = "Languages"

    _disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys()
    _disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice

    def install_lang(self, cr, uid, **args):
        """

        This method is called from ecore/addons/base/base_data.xml to load
        some language and set it as the default for every partners. The
        language is set via tools.config by the RPC 'create' method on the
        'db' object. This is a fragile solution and something else should be
        found.

        """
        lang = tools.config.get('lang')
        if not lang:
            return False
        lang_ids = self.search(cr, uid, [('code','=', lang)])
        if not lang_ids:
            self.load_lang(cr, uid, lang)
        ir_values_obj = self.pool.get('ir.values')
        default_value = ir_values_obj.get(cr, uid, 'default', False, ['res.partner'])
        if not default_value:
            ir_values_obj.set(cr, uid, 'default', False, 'lang', ['res.partner'], lang)
        return True

    def load_lang(self, cr, uid, lang, lang_name=None):
        # create the language with locale information
        fail = True
        iso_lang = tools.get_iso_codes(lang)
        for ln in tools.get_locales(lang):
            try:
                locale.setlocale(locale.LC_ALL, str(ln))
                fail = False
                break
            except locale.Error:
                continue
        if fail:
            lc = locale.getdefaultlocale()[0]
            msg = 'Unable to get information for locale %s. Information from the default locale (%s) have been used.'
            _logger.warning(msg, lang, lc)

        if not lang_name:
            lang_name = tools.ALL_LANGUAGES.get(lang, lang)


        def fix_xa0(s):
            """Fix badly-encoded non-breaking space Unicode character from locale.localeconv(),
               coercing to utf-8, as some platform seem to output localeconv() in their system
               encoding, e.g. Windows-1252"""
            if s == '\xa0':
                return '\xc2\xa0'
            return s

        def fix_datetime_format(format):
            """Python's strftime supports only the format directives
               that are available on the platform's libc, so in order to
               be 100% cross-platform we map to the directives required by
               the C standard (1989 version), always available on platforms
               with a C standard implementation."""
            # For some locales, nl_langinfo returns a D_FMT/T_FMT that contains
            # unsupported '%-' patterns, e.g. for cs_CZ
            format = format.replace('%-', '%')

            for pattern, replacement in tools.DATETIME_FORMATS_MAP.iteritems():
                format = format.replace(pattern, replacement)
            return str(format)

        lang_info = {
            'code': lang,
            'iso_code': iso_lang,
            'name': lang_name,
            'translatable': 1,
            'date_format' : fix_datetime_format(locale.nl_langinfo(locale.D_FMT)),
            'time_format' : fix_datetime_format(locale.nl_langinfo(locale.T_FMT)),
            'decimal_point' : fix_xa0(str(locale.localeconv()['decimal_point'])),
            'thousands_sep' : fix_xa0(str(locale.localeconv()['thousands_sep'])),
        }
        lang_id = False
        try:
            lang_id = self.create(cr, uid, lang_info)
        finally:
            tools.resetlocale()
        return lang_id

    def _check_format(self, cr, uid, ids, context=None):
        for lang in self.browse(cr, uid, ids, context=context):
            for pattern in self._disallowed_datetime_patterns:
                if (lang.time_format and pattern in lang.time_format)\
                    or (lang.date_format and pattern in lang.date_format):
                    return False
        return True

    def _check_grouping(self, cr, uid, ids, context=None):
        for lang in self.browse(cr, uid, ids, context=context):
            try:
                if not all(isinstance(x, int) for x in eval(lang.grouping)):
                    return False
            except Exception:
                return False
        return True

    def _get_default_date_format(self, cursor, user, context=None):
        return '%m/%d/%Y'

    def _get_default_time_format(self, cursor, user, context=None):
        return '%H:%M:%S'

    _columns = {
        'name': fields.char('Name', required=True),
        'code': fields.char('Locale Code', size=16, required=True, help='This field is used to set/get locales for user'),
        'iso_code': fields.char('ISO code', size=16, required=False, help='This ISO code is the name of po files to use for translations'),
        'translatable': fields.boolean('Translatable'),
        'active': fields.boolean('Active'),
        'direction': fields.selection([('ltr', 'Left-to-Right'), ('rtl', 'Right-to-Left')], 'Direction', required=True),
        'date_format':fields.char('Date Format', required=True),
        'time_format':fields.char('Time Format', required=True),
        'grouping':fields.char('Separator Format', required=True,help="The Separator Format should be like [,n] where 0 < n :starting from Unit digit.-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. Provided ',' as the thousand separator in each case."),
        'decimal_point':fields.char('Decimal Separator', required=True),
        'thousands_sep':fields.char('Thousands Separator'),
    }
    _defaults = {
        'active': 1,
        'translatable': 0,
        'direction': 'ltr',
        'date_format':_get_default_date_format,
        'time_format':_get_default_time_format,
        'grouping': '[]',
        'decimal_point': '.',
        'thousands_sep': ',',
    }
    _sql_constraints = [
        ('name_uniq', 'unique (name)', 'The name of the language must be unique !'),
        ('code_uniq', 'unique (code)', 'The code of the language must be unique !'),
    ]

    _constraints = [
        (_check_format, 'Invalid date/time format directive specified. Please refer to the list of allowed directives, displayed when you edit a language.', ['time_format', 'date_format']),
        (_check_grouping, "The Separator Format should be like [,n] where 0 < n :starting from Unit digit.-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. Provided ',' as the thousand separator in each case.", ['grouping'])
    ]

    @tools.ormcache('lang')
    def _lang_get(self, cr, uid, lang):
        lang_ids = self.search(cr, uid, [('code', '=', lang)]) or \
                   self.search(cr, uid, [('code', '=', 'en_US')])
        return lang_ids[0]

    @tools.ormcache('lang', 'monetary')
    def _lang_data_get(self, cr, uid, lang, monetary=False):
        if type(lang) in (str, unicode):
            lang = self._lang_get(cr, uid, lang)
        conv = localeconv()
        lang_obj = self.browse(cr, uid, lang)
        thousands_sep = lang_obj.thousands_sep or conv[monetary and 'mon_thousands_sep' or 'thousands_sep']
        decimal_point = lang_obj.decimal_point
        grouping = lang_obj.grouping
        return grouping, thousands_sep, decimal_point

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

        if 'code' in vals:
            for rec in self.browse(cr, uid, ids, context):
                if rec.code != vals['code']:
                    raise UserError(_("Language code cannot be modified."))

        if vals.get('active') == False:
            users = self.pool.get('res.users')
            if self.search_count(cr, uid, [('id', 'in', ids), ('code', '=', 'en_US')], context=context):
                raise UserError(_("Base Language 'en_US' can not be deactivated!"))

            for current_id in ids:
                current_language = self.browse(cr, uid, current_id, context=context)
                if users.search(cr, uid, [('lang', '=', current_language.code)], context=context):
                    raise UserError(_("Cannot unactivate a language that is currently used by users."))

        self._lang_get.clear_cache(self)
        self._lang_data_get.clear_cache(self)
        return super(lang, self).write(cr, uid, ids, vals, context)

    def unlink(self, cr, uid, ids, context=None):
        if context is None:
            context = {}
        languages = self.read(cr, uid, ids, ['code','active'], context=context)
        for language in languages:
            ctx_lang = context.get('lang')
            if language['code']=='en_US':
                raise UserError(_("Base Language 'en_US' can not be deleted!"))
            if ctx_lang and (language['code']==ctx_lang):
                raise UserError(_("You cannot delete the language which is User's Preferred Language!"))
            if language['active']:
                raise UserError(_("You cannot delete the language which is Active!\nPlease de-activate the language first."))
            trans_obj = self.pool.get('ir.translation')
            trans_ids = trans_obj.search(cr, uid, [('lang','=',language['code'])], context=context)
            trans_obj.unlink(cr, uid, trans_ids, context=context)
        self._lang_get.clear_cache(self)
        self._lang_data_get.clear_cache(self)
        return super(lang, self).unlink(cr, uid, ids, context=context)

    #
    # IDS: can be a list of IDS or a list of XML_IDS
    #
    def format(self, cr, uid, ids, percent, value, grouping=False, monetary=False, context=None):
        """ Format() will return the language-specific output for float values"""
        if percent[0] != '%':
            raise ValueError("format() must be given exactly one %char format specifier")

        formatted = percent % value

        # floats and decimal ints need special action!
        if grouping:
            lang_grouping, thousands_sep, decimal_point = \
                self._lang_data_get(cr, uid, ids[0], monetary)
            eval_lang_grouping = eval(lang_grouping)

            if percent[-1] in 'eEfFgG':
                parts = formatted.split('.')
                parts[0], _ = intersperse(parts[0], eval_lang_grouping, thousands_sep)

                formatted = decimal_point.join(parts)

            elif percent[-1] in 'diu':
                formatted = intersperse(formatted, eval_lang_grouping, thousands_sep)[0]

        return formatted
Beispiel #18
0
class website_config_settings(osv.osv_memory):
    _name = 'website.config.settings'
    _inherit = 'res.config.settings'

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

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

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

    _defaults = {
        'website_id':
        lambda self, cr, uid, c: self.pool.get('website').search(
            cr, uid, [], context=c)[0],
    }
Beispiel #19
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 ecore.report.interface.report_int._reports:
            new_report = ecore.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'])(ecore.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 #20
0
class payroll_advice(osv.osv):
    '''
    Bank Advice
    '''
    _name = 'hr.payroll.advice'
    _description = 'Bank Advice'
    _columns = {
        'name':
        fields.char(
            'Name',
            readonly=True,
            required=True,
            states={'draft': [('readonly', False)]},
        ),
        'note':
        fields.text('Description'),
        'date':
        fields.date('Date',
                    readonly=True,
                    required=True,
                    states={'draft': [('readonly', False)]},
                    help="Advice Date is used to search Payslips"),
        'state':
        fields.selection([
            ('draft', 'Draft'),
            ('confirm', 'Confirmed'),
            ('cancel', 'Cancelled'),
        ],
                         'Status',
                         select=True,
                         readonly=True),
        'number':
        fields.char('Reference', readonly=True),
        'line_ids':
        fields.one2many('hr.payroll.advice.line',
                        'advice_id',
                        'Employee Salary',
                        states={'draft': [('readonly', False)]},
                        readonly=True,
                        copy=True),
        'chaque_nos':
        fields.char('Cheque Numbers'),
        'neft':
        fields.boolean(
            'NEFT Transaction',
            help="Check this box if your company use online transfer for salary"
        ),
        'company_id':
        fields.many2one('res.company',
                        'Company',
                        required=True,
                        readonly=True,
                        states={'draft': [('readonly', False)]}),
        'bank_id':
        fields.many2one(
            'res.bank',
            'Bank',
            readonly=True,
            states={'draft': [('readonly', False)]
                    },
            help="Select the Bank from which the salary is going to be paid"),
        'batch_id':
        fields.many2one('hr.payslip.run', 'Batch', readonly=True)
    }

    _defaults = {
        'date': lambda * a: time.strftime('%Y-%m-%d'),
        'state': lambda * a: 'draft',
        'company_id': lambda self, cr, uid, context: \
                self.pool.get('res.users').browse(cr, uid, uid,
                    context=context).company_id.id,
        'note': "Please make the payroll transfer from above account number to the below mentioned account numbers towards employee salaries:"
    }

    def compute_advice(self, cr, uid, ids, context=None):
        """
        Advice - Create Advice lines in Payment Advice and
        compute Advice lines.
        @param cr: the current row, from the database cursor,
        @param uid: the current user’s ID for security checks,
        @param ids: List of Advice’s IDs
        @return: Advice lines
        @param context: A standard dictionary for contextual values
        """
        payslip_pool = self.pool.get('hr.payslip')
        advice_line_pool = self.pool.get('hr.payroll.advice.line')
        payslip_line_pool = self.pool.get('hr.payslip.line')

        for advice in self.browse(cr, uid, ids, context=context):
            old_line_ids = advice_line_pool.search(
                cr, uid, [('advice_id', '=', advice.id)], context=context)
            if old_line_ids:
                advice_line_pool.unlink(cr, uid, old_line_ids, context=context)
            slip_ids = payslip_pool.search(cr,
                                           uid,
                                           [('date_from', '<=', advice.date),
                                            ('date_to', '>=', advice.date),
                                            ('state', '=', 'done')],
                                           context=context)
            for slip in payslip_pool.browse(cr, uid, slip_ids,
                                            context=context):
                if not slip.employee_id.bank_account_id and not slip.employee_id.bank_account_id.acc_number:
                    raise UserError(
                        _('Please define bank account for the %s employee') %
                        (slip.employee_id.name, ))
                line_ids = payslip_line_pool.search(cr,
                                                    uid,
                                                    [('slip_id', '=', slip.id),
                                                     ('code', '=', 'NET')],
                                                    context=context)
                if line_ids:
                    line = payslip_line_pool.browse(cr,
                                                    uid,
                                                    line_ids,
                                                    context=context)[0]
                    advice_line = {
                        'advice_id': advice.id,
                        'name': slip.employee_id.bank_account_id.acc_number,
                        'employee_id': slip.employee_id.id,
                        'bysal': line.total
                    }
                    advice_line_pool.create(cr,
                                            uid,
                                            advice_line,
                                            context=context)
                payslip_pool.write(cr,
                                   uid,
                                   slip_ids, {'advice_id': advice.id},
                                   context=context)
        return True

    def confirm_sheet(self, cr, uid, ids, context=None):
        """
        confirm Advice - confirmed Advice after computing Advice Lines..
        @param cr: the current row, from the database cursor,
        @param uid: the current user’s ID for security checks,
        @param ids: List of confirm Advice’s IDs
        @return: confirmed Advice lines and set sequence of Advice.
        @param context: A standard dictionary for contextual values
        """
        seq_obj = self.pool.get('ir.sequence')
        for advice in self.browse(cr, uid, ids, context=context):
            if not advice.line_ids:
                raise UserError(
                    _('You can not confirm Payment advice without advice lines.'
                      ))
            advice_date = datetime.strptime(advice.date, DATETIME_FORMAT)
            advice_year = advice_date.strftime(
                '%m') + '-' + advice_date.strftime('%Y')
            number = seq_obj.next_by_code(cr, uid, 'payment.advice')
            sequence_num = 'PAY' + '/' + advice_year + '/' + number
            self.write(cr,
                       uid, [advice.id], {
                           'number': sequence_num,
                           'state': 'confirm'
                       },
                       context=context)
        return True

    def set_to_draft(self, cr, uid, ids, context=None):
        """Resets Advice as draft.
        """
        return self.write(cr, uid, ids, {'state': 'draft'}, context=context)

    def cancel_sheet(self, cr, uid, ids, context=None):
        """Marks Advice as cancelled.
        """
        return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)

    def onchange_company_id(self,
                            cr,
                            uid,
                            ids,
                            company_id=False,
                            context=None):
        res = {}
        if company_id:
            company = self.pool.get('res.company').browse(cr,
                                                          uid, [company_id],
                                                          context=context)[0]
            if company.partner_id.bank_ids:
                res.update(
                    {'bank_id': company.partner_id.bank_ids[0].bank_id.id})
        return {'value': res}
Beispiel #21
0
class payslip_report(osv.osv):
    _name = "payslip.report"
    _description = "Payslip Analysis"
    _auto = False
    _columns = {
        'name':
        fields.char('Name', readonly=True),
        'date_from':
        fields.date(
            'Date From',
            readonly=True,
        ),
        'date_to':
        fields.date(
            'Date To',
            readonly=True,
        ),
        'year':
        fields.char('Year', size=4, readonly=True),
        'month':
        fields.selection([('01', 'January'), ('02', 'February'),
                          ('03', 'March'), ('04', 'April'), ('05', 'May'),
                          ('06', 'June'), ('07', 'July'), ('08', 'August'),
                          ('09', 'September'), ('10', 'October'),
                          ('11', 'November'), ('12', 'December')],
                         'Month',
                         readonly=True),
        'day':
        fields.char('Day', size=128, readonly=True),
        'state':
        fields.selection([
            ('draft', 'Draft'),
            ('done', 'Done'),
            ('cancel', 'Rejected'),
        ],
                         'Status',
                         readonly=True),
        'employee_id':
        fields.many2one('hr.employee', 'Employee', readonly=True),
        'nbr':
        fields.integer('# Payslip lines', readonly=True),
        'number':
        fields.char('Number', readonly=True),
        'struct_id':
        fields.many2one('hr.payroll.structure', 'Structure', readonly=True),
        'company_id':
        fields.many2one('res.company', 'Company', readonly=True),
        'paid':
        fields.boolean('Made Payment Order ? ', readonly=True),
        'total':
        fields.float('Total', readonly=True),
        'category_id':
        fields.many2one('hr.salary.rule.category', 'Category', readonly=True),
    }

    def init(self, cr):
        tools.drop_view_if_exists(cr, 'payslip_report')
        cr.execute("""
            create or replace view payslip_report as (
                select
                    min(l.id) as id,
                    l.name,
                    p.struct_id,
                    p.state,
                    p.date_from,
                    p.date_to,
                    p.number,
                    p.company_id,
                    p.paid,
                    l.category_id,
                    l.employee_id,
                    sum(l.total) as total,
                    to_char(p.date_from, 'YYYY') as year,
                    to_char(p.date_from, 'MM') as month,
                    to_char(p.date_from, 'YYYY-MM-DD') as day,
                    to_char(p.date_to, 'YYYY') as to_year,
                    to_char(p.date_to, 'MM') as to_month,
                    to_char(p.date_to, 'YYYY-MM-DD') as to_day,
                    1 AS nbr
                from
                    hr_payslip as p
                    left join hr_payslip_line as l on (p.id=l.slip_id)
                where 
                    l.employee_id IS NOT NULL
                group by
                    p.number,l.name,p.date_from,p.date_to,p.state,p.company_id,p.paid,
                    l.employee_id,p.struct_id,l.category_id
            )
        """)
Beispiel #22
0
class res_company(osv.osv):
    _name = "res.company"
    _description = 'Companies'
    _order = 'name'

    def _get_address_data(self, cr, uid, ids, field_names, arg, context=None):
        """ Read the 'address' functional fields. """
        result = {}
        part_obj = self.pool.get('res.partner')
        for company in self.browse(cr, uid, ids, context=context):
            result[company.id] = {}.fromkeys(field_names, False)
            if company.partner_id:
                address_data = part_obj.address_get(cr,
                                                    ecore.SUPERUSER_ID,
                                                    [company.partner_id.id],
                                                    adr_pref=['contact'])
                if address_data['contact']:
                    address = part_obj.read(cr,
                                            ecore.SUPERUSER_ID,
                                            [address_data['contact']],
                                            field_names,
                                            context=context)[0]
                    for field in field_names:
                        result[company.id][field] = address[field] or False
        return result

    def _set_address_data(self,
                          cr,
                          uid,
                          company_id,
                          name,
                          value,
                          arg,
                          context=None):
        """ Write the 'address' functional fields. """
        company = self.browse(cr, uid, company_id, context=context)
        if company.partner_id:
            part_obj = self.pool.get('res.partner')
            address_data = part_obj.address_get(cr,
                                                uid, [company.partner_id.id],
                                                adr_pref=['contact'])
            address = address_data['contact']
            if address:
                part_obj.write(cr,
                               uid, [address], {name: value or False},
                               context=context)
            else:
                part_obj.create(cr,
                                uid, {
                                    name: value or False,
                                    'parent_id': company.partner_id.id
                                },
                                context=context)
        return True

    def _get_logo_web(self, cr, uid, ids, _field_name, _args, context=None):
        result = dict.fromkeys(ids, False)
        for record in self.browse(cr, uid, ids, context=context):
            size = (180, None)
            result[record.id] = image_resize_image(record.partner_id.image,
                                                   size)
        return result

    def _get_companies_from_partner(self, cr, uid, ids, context=None):
        return self.pool['res.company'].search(cr,
                                               uid,
                                               [('partner_id', 'in', ids)],
                                               context=context)

    _columns = {
        'name':
        fields.related('partner_id',
                       'name',
                       string='Company Name',
                       size=128,
                       required=True,
                       store=True,
                       type='char'),
        'parent_id':
        fields.many2one('res.company', 'Parent Company', select=True),
        'child_ids':
        fields.one2many('res.company', 'parent_id', 'Child Companies'),
        'partner_id':
        fields.many2one('res.partner', 'Partner', required=True),
        'rml_header':
        fields.text('RML Header', required=True),
        'rml_header1':
        fields.char(
            'Company Tagline',
            help=
            "Appears by default on the top right corner of your printed documents (report header)."
        ),
        'rml_header2':
        fields.text('RML Internal Header', required=True),
        'rml_header3':
        fields.text('RML Internal Header for Landscape Reports',
                    required=True),
        'rml_footer':
        fields.text(
            'Report Footer',
            help="Footer text displayed at the bottom of all reports."),
        'rml_footer_readonly':
        fields.related('rml_footer',
                       type='text',
                       string='Report Footer',
                       readonly=True),
        'custom_footer':
        fields.boolean(
            'Custom Footer',
            help=
            "Check this to define the report footer manually.  Otherwise it will be filled in automatically."
        ),
        'font':
        fields.many2one(
            'res.font',
            string="Font",
            domain=[('mode', 'in', ('Normal', 'Regular', 'all', 'Book'))],
            help=
            "Set the font into the report header, it will be used as default font in the RML reports of the user company"
        ),
        'logo':
        fields.related('partner_id', 'image', string="Logo", type="binary"),
        # logo_web: do not store in attachments, since the image is retrieved in SQL for
        # performance reasons (see addons/web/controllers/main.py, Binary.company_logo)
        'logo_web':
        fields.function(_get_logo_web,
                        string="Logo Web",
                        type="binary",
                        store={
                            'res.company':
                            (lambda s, c, u, i, x: i, ['partner_id'], 10),
                            'res.partner':
                            (_get_companies_from_partner, ['image'], 10),
                        }),
        'currency_id':
        fields.many2one('res.currency', 'Currency', required=True),
        'user_ids':
        fields.many2many('res.users', 'res_company_users_rel', 'cid',
                         'user_id', 'Accepted Users'),
        'account_no':
        fields.char('Account No.'),
        'street':
        fields.function(_get_address_data,
                        fnct_inv=_set_address_data,
                        size=128,
                        type='char',
                        string="Street",
                        multi='address'),
        'street2':
        fields.function(_get_address_data,
                        fnct_inv=_set_address_data,
                        size=128,
                        type='char',
                        string="Street2",
                        multi='address'),
        'zip':
        fields.function(_get_address_data,
                        fnct_inv=_set_address_data,
                        size=24,
                        type='char',
                        string="Zip",
                        multi='address'),
        'city':
        fields.function(_get_address_data,
                        fnct_inv=_set_address_data,
                        size=24,
                        type='char',
                        string="City",
                        multi='address'),
        'state_id':
        fields.function(_get_address_data,
                        fnct_inv=_set_address_data,
                        type='many2one',
                        relation='res.country.state',
                        string="Fed. State",
                        multi='address'),
        'country_id':
        fields.function(_get_address_data,
                        fnct_inv=_set_address_data,
                        type='many2one',
                        relation='res.country',
                        string="Country",
                        multi='address'),
        'email':
        fields.related('partner_id',
                       'email',
                       size=64,
                       type='char',
                       string="Email",
                       store=True),
        'phone':
        fields.related('partner_id',
                       'phone',
                       size=64,
                       type='char',
                       string="Phone",
                       store=True),
        'fax':
        fields.function(_get_address_data,
                        fnct_inv=_set_address_data,
                        size=64,
                        type='char',
                        string="Fax",
                        multi='address'),
        'website':
        fields.related('partner_id',
                       'website',
                       string="Website",
                       type="char",
                       size=64),
        'vat':
        fields.related('partner_id',
                       'vat',
                       string="Tax ID",
                       type="char",
                       size=32),
        'company_registry':
        fields.char('Company Registry', size=64),
        'rml_paper_format':
        fields.selection([('a4', 'A4'), ('us_letter', 'US Letter')],
                         "Paper Format",
                         required=True,
                         oldname='paper_format'),
    }
    _sql_constraints = [('name_uniq', 'unique (name)',
                         'The company name must be unique !')]

    @api.onchange('custom_footer', 'phone', 'fax', 'email', 'website', 'vat',
                  'company_registry')
    def onchange_footer(self):
        if not self.custom_footer:
            # first line (notice that missing elements are filtered out before the join)
            res = ' | '.join(
                filter(bool, [
                    self.phone and '%s: %s' % (_('Phone'), self.phone),
                    self.fax and '%s: %s' % (_('Fax'), self.fax),
                    self.email and '%s: %s' % (_('Email'), self.email),
                    self.website and '%s: %s' % (_('Website'), self.website),
                    self.vat and '%s: %s' % (_('TIN'), self.vat),
                    self.company_registry and '%s: %s' %
                    (_('Reg'), self.company_registry),
                ]))
            self.rml_footer_readonly = res
            self.rml_footer = res

    def onchange_state(self, cr, uid, ids, state_id, context=None):
        if state_id:
            return {
                'value': {
                    'country_id':
                    self.pool.get('res.country.state').browse(
                        cr, uid, state_id, context).country_id.id
                }
            }
        return {}

    def onchange_font_name(self,
                           cr,
                           uid,
                           ids,
                           font,
                           rml_header,
                           rml_header2,
                           rml_header3,
                           context=None):
        """ To change default header style of all <para> and drawstring. """
        def _change_header(header, font):
            """ Replace default fontname use in header and setfont tag """

            default_para = re.sub('fontName.?=.?".*"', 'fontName="%s"' % font,
                                  header)
            return re.sub('(<setFont.?name.?=.?)(".*?")(.)',
                          '\g<1>"%s"\g<3>' % font, default_para)

        if not font:
            return True
        fontname = self.pool.get('res.font').browse(cr,
                                                    uid,
                                                    font,
                                                    context=context).name
        return {
            'value': {
                'rml_header': _change_header(rml_header, fontname),
                'rml_header2': _change_header(rml_header2, fontname),
                'rml_header3': _change_header(rml_header3, fontname)
            }
        }

    def on_change_country(self, cr, uid, ids, country_id, context=None):
        res = {'domain': {'state_id': []}}
        currency_id = self._get_euro(cr, uid, context=context)
        if country_id:
            currency_id = self.pool.get('res.country').browse(
                cr, uid, country_id, context=context).currency_id.id
            res['domain'] = {'state_id': [('country_id', '=', country_id)]}
        res['value'] = {'currency_id': currency_id}
        return res

    def name_search(self,
                    cr,
                    uid,
                    name='',
                    args=None,
                    operator='ilike',
                    context=None,
                    limit=100):
        context = dict(context or {})
        if context.pop('user_preference', None):
            # We browse as superuser. Otherwise, the user would be able to
            # select only the currently visible companies (according to rules,
            # which are probably to allow to see the child companies) even if
            # she belongs to some other companies.
            user = self.pool.get('res.users').browse(cr,
                                                     SUPERUSER_ID,
                                                     uid,
                                                     context=context)
            cmp_ids = list(
                set([user.company_id.id] +
                    [cmp.id for cmp in user.company_ids]))
            uid = SUPERUSER_ID
            args = (args or []) + [('id', 'in', cmp_ids)]
        return super(res_company, self).name_search(cr,
                                                    uid,
                                                    name=name,
                                                    args=args,
                                                    operator=operator,
                                                    context=context,
                                                    limit=limit)

    @api.returns('self')
    def _company_default_get(self,
                             cr,
                             uid,
                             object=False,
                             field=False,
                             context=None):
        """
        Returns the default company (the user's company)
        The 'object' and 'field' arguments are ignored but left here for
        backward compatibility and potential override.
        """
        return self.pool['res.users']._get_company(cr, uid, context=context)

    @tools.ormcache('uid', 'company')
    def _get_company_children(self, cr, uid=None, company=None):
        if not company:
            return []
        ids = self.search(cr, uid, [('parent_id', 'child_of', [company])])
        return ids

    def _get_partner_hierarchy(self, cr, uid, company_id, context=None):
        if company_id:
            parent_id = self.browse(cr, uid, company_id)['parent_id']
            if parent_id:
                return self._get_partner_hierarchy(cr, uid, parent_id.id,
                                                   context)
            else:
                return self._get_partner_descendance(cr, uid, company_id, [],
                                                     context)
        return []

    def _get_partner_descendance(self,
                                 cr,
                                 uid,
                                 company_id,
                                 descendance,
                                 context=None):
        descendance.append(self.browse(cr, uid, company_id).partner_id.id)
        for child_id in self._get_company_children(cr, uid, company_id):
            if child_id != company_id:
                descendance = self._get_partner_descendance(
                    cr, uid, child_id, descendance)
        return descendance

    #
    # This function restart the cache on the _get_company_children method
    #
    def cache_restart(self, cr):
        self._get_company_children.clear_cache(self)

    def create(self, cr, uid, vals, context=None):
        if not vals.get('name', False) or vals.get('partner_id', False):
            self.cache_restart(cr)
            return super(res_company, self).create(cr,
                                                   uid,
                                                   vals,
                                                   context=context)
        obj_partner = self.pool.get('res.partner')
        partner_id = obj_partner.create(cr,
                                        uid, {
                                            'name': vals['name'],
                                            'is_company': True,
                                            'image': vals.get('logo', False)
                                        },
                                        context=context)
        vals.update({'partner_id': partner_id})
        self.cache_restart(cr)
        company_id = super(res_company, self).create(cr,
                                                     uid,
                                                     vals,
                                                     context=context)
        obj_partner.write(cr,
                          uid, [partner_id], {'company_id': company_id},
                          context=context)
        return company_id

    def write(self, cr, uid, ids, values, context=None):
        self.cache_restart(cr)
        return super(res_company, self).write(cr,
                                              uid,
                                              ids,
                                              values,
                                              context=context)

    def _get_euro(self, cr, uid, context=None):
        rate_obj = self.pool.get('res.currency.rate')
        rate_id = rate_obj.search(cr, uid, [('rate', '=', 1)], context=context)
        return rate_id and rate_obj.browse(
            cr, uid, rate_id[0], context=context).currency_id.id or False

    def _get_logo(self, cr, uid, ids):
        return open(
            os.path.join(tools.config['root_path'], 'addons', 'base', 'res',
                         'res_company_logo.png'), 'rb').read().encode('base64')

    def _get_font(self, cr, uid, ids):
        font_obj = self.pool.get('res.font')
        res = font_obj.search(cr,
                              uid, [('family', '=', 'Helvetica'),
                                    ('mode', '=', 'all')],
                              limit=1)
        return res and res[0] or False

    _header = """
<header>
<pageTemplate>
    <frame id="first" x1="28.0" y1="28.0" width="%s" height="%s"/>
    <stylesheet>
       <!-- Set here the default font to use for all <para> tags -->
       <paraStyle name='Normal' fontName="DejaVuSans"/>
    </stylesheet>
    <pageGraphics>
        <fill color="black"/>
        <stroke color="black"/>
        <setFont name="DejaVuSans" size="8"/>
        <drawString x="%s" y="%s"> [[ formatLang(time.strftime("%%Y-%%m-%%d"), date=True) ]]  [[ time.strftime("%%H:%%M") ]]</drawString>
        <setFont name="DejaVuSans-Bold" size="10"/>
        <drawCentredString x="%s" y="%s">[[ company.partner_id.name ]]</drawCentredString>
        <stroke color="#000000"/>
        <lines>%s</lines>
        <!-- Set here the default font to use for all <drawString> tags -->
        <!-- don't forget to change the 2 other occurence of <setFont> above if needed --> 
        <setFont name="DejaVuSans" size="8"/>
    </pageGraphics>
</pageTemplate>
</header>"""

    _header2 = _header % (539, 772, "1.0cm", "28.3cm", "11.1cm", "28.3cm",
                          "1.0cm 28.1cm 20.1cm 28.1cm")

    _header3 = _header % (786, 525, 25, 555, 440, 555, "25 550 818 550")

    def _get_header(self, cr, uid, ids):
        try:
            header_file = tools.file_open(
                os.path.join('base', 'report', 'corporate_rml_header.rml'))
            try:
                return header_file.read()
            finally:
                header_file.close()
        except:
            return self._header_a4

    _header_main = """
<header>
    <pageTemplate>
        <frame id="first" x1="1.3cm" y1="3.0cm" height="%s" width="19.0cm"/>
         <stylesheet>
            <!-- Set here the default font to use for all <para> tags -->
            <paraStyle name='Normal' fontName="DejaVuSans"/>
            <paraStyle name="main_footer" fontSize="8.0" alignment="CENTER"/>
            <paraStyle name="main_header" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
         </stylesheet>
        <pageGraphics>
            <!-- Set here the default font to use for all <drawString> tags -->
            <setFont name="DejaVuSans" size="8"/>
            <!-- You Logo - Change X,Y,Width and Height -->
            <image x="1.3cm" y="%s" height="40.0" >[[ company.logo or removeParentNode('image') ]]</image>
            <fill color="black"/>
            <stroke color="black"/>

            <!-- page header -->
            <lines>1.3cm %s 20cm %s</lines>
            <drawRightString x="20cm" y="%s">[[ company.rml_header1 ]]</drawRightString>
            <drawString x="1.3cm" y="%s">[[ company.partner_id.name ]]</drawString>
            <place x="1.3cm" y="%s" height="1.8cm" width="15.0cm">
                <para style="main_header">[[ display_address(company.partner_id) or  '' ]]</para>
            </place>
            <drawString x="1.3cm" y="%s">Phone:</drawString>
            <drawRightString x="7cm" y="%s">[[ company.partner_id.phone or '' ]]</drawRightString>
            <drawString x="1.3cm" y="%s">Mail:</drawString>
            <drawRightString x="7cm" y="%s">[[ company.partner_id.email or '' ]]</drawRightString>
            <lines>1.3cm %s 7cm %s</lines>

            <!-- left margin -->
            <rotate degrees="90"/>
            <fill color="grey"/>
            <drawString x="2.65cm" y="-0.4cm">generated by eCore.com</drawString>
            <fill color="black"/>
            <rotate degrees="-90"/>

            <!--page bottom-->
            <lines>1.2cm 2.65cm 19.9cm 2.65cm</lines>
            <place x="1.3cm" y="0cm" height="2.55cm" width="19.0cm">
                <para style="main_footer">[[ company.rml_footer ]]</para>
                <para style="main_footer">Contact : [[ user.name ]] - Page: <pageNumber/></para>
            </place>
        </pageGraphics>
    </pageTemplate>
</header>"""

    _header_a4 = _header_main % (
        '21.7cm', '27.7cm', '27.7cm', '27.7cm', '27.8cm', '27.3cm', '25.3cm',
        '25.0cm', '25.0cm', '24.6cm', '24.6cm', '24.5cm', '24.5cm')
    _header_letter = _header_main % (
        '20cm', '26.0cm', '26.0cm', '26.0cm', '26.1cm', '25.6cm', '23.6cm',
        '23.3cm', '23.3cm', '22.9cm', '22.9cm', '22.8cm', '22.8cm')

    def onchange_rml_paper_format(self,
                                  cr,
                                  uid,
                                  ids,
                                  rml_paper_format,
                                  context=None):
        if rml_paper_format == 'us_letter':
            return {'value': {'rml_header': self._header_letter}}
        return {'value': {'rml_header': self._header_a4}}

    def act_discover_fonts(self, cr, uid, ids, context=None):
        return self.pool.get("res.font").font_scan(cr, uid, context=context)

    _defaults = {
        'currency_id': _get_euro,
        'rml_paper_format': 'a4',
        'rml_header': _get_header,
        'rml_header2': _header2,
        'rml_header3': _header3,
        'logo': _get_logo,
        'font': _get_font,
    }

    _constraints = [
        (osv.osv._check_recursion,
         'Error! You can not create recursive companies.', ['parent_id'])
    ]
Beispiel #23
0
class gamification_goal(osv.Model):
    """Goal instance for a user

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

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

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

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

    _columns = {
        'definition_id': fields.many2one('gamification.goal.definition', string='Goal Definition', required=True, ondelete="cascade"),
        'user_id': fields.many2one('res.users', string='User', required=True, auto_join=True, ondelete="cascade"),
        'line_id': fields.many2one('gamification.challenge.line', string='Challenge Line', ondelete="cascade"),
        'challenge_id': fields.related('line_id', 'challenge_id',
            string="Challenge",
            type='many2one',
            relation='gamification.challenge',
            store=True, readonly=True,
            help="Challenge that generated the goal, assign challenge to users to generate goals with a value in this field."),
        'start_date': fields.date('Start Date'),
        'end_date': fields.date('End Date'),  # no start and end = always active
        'target_goal': fields.float('To Reach',
            required=True,
            track_visibility='always'),  # no goal = global index
        'current': fields.float('Current Value', required=True, track_visibility='always'),
        'completeness': fields.function(_get_completion, type='float', string='Completeness'),
        'state': fields.selection([
                ('draft', 'Draft'),
                ('inprogress', 'In progress'),
                ('reached', 'Reached'),
                ('failed', 'Failed'),
                ('canceled', 'Canceled'),
            ],
            string='State',
            required=True,
            track_visibility='always'),
        'to_update': fields.boolean('To update'),
        'closed': fields.boolean('Closed goal', help="These goals will not be recomputed."),

        'computation_mode': fields.related('definition_id', 'computation_mode', type='char', string="Computation mode"),
        'remind_update_delay': fields.integer('Remind delay',
            help="The number of days after which the user assigned to a manual goal will be reminded. Never reminded if no value is specified."),
        'last_update': fields.date('Last Update',
            help="In case of manual goal, reminders are sent if the goal as not been updated for a while (defined in challenge). Ignored in case of non-manual goal or goal not linked to a challenge."),

        'definition_description': fields.related('definition_id', 'description', type='char', string='Definition Description', readonly=True),
        'definition_condition': fields.related('definition_id', 'condition', type='char', string='Definition Condition', readonly=True),
        'definition_suffix': fields.related('definition_id', 'full_suffix', type="char", string="Suffix", readonly=True),
        'definition_display': fields.related('definition_id', 'display_mode', type="char", string="Display Mode", readonly=True),
    }

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

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

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

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

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

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

        return result

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

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

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

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

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

            else:  # count or sum

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return False
Beispiel #24
0
class payment_advice_report(osv.osv):
    _name = "payment.advice.report"
    _description = "Payment Advice Analysis"
    _auto = False
    _columns = {
        'name':
        fields.char('Name', readonly=True),
        'date':
        fields.date(
            'Date',
            readonly=True,
        ),
        'year':
        fields.char('Year', size=4, readonly=True),
        'month':
        fields.selection([('01', 'January'), ('02', 'February'),
                          ('03', 'March'), ('04', 'April'), ('05', 'May'),
                          ('06', 'June'), ('07', 'July'), ('08', 'August'),
                          ('09', 'September'), ('10', 'October'),
                          ('11', 'November'), ('12', 'December')],
                         'Month',
                         readonly=True),
        'day':
        fields.char('Day', size=128, readonly=True),
        'state':
        fields.selection([
            ('draft', 'Draft'),
            ('confirm', 'Confirmed'),
            ('cancel', 'Cancelled'),
        ],
                         'Status',
                         select=True,
                         readonly=True),
        'employee_id':
        fields.many2one('hr.employee', 'Employee', readonly=True),
        'nbr':
        fields.integer('# Payment Lines', readonly=True),
        'number':
        fields.char('Number', readonly=True),
        'bysal':
        fields.float('By Salary', readonly=True),
        'bank_id':
        fields.many2one('res.bank', 'Bank', readonly=True),
        'company_id':
        fields.many2one('res.company', 'Company', readonly=True),
        'cheque_nos':
        fields.char('Cheque Numbers', readonly=True),
        'neft':
        fields.boolean('NEFT Transaction', readonly=True),
        'ifsc_code':
        fields.char('IFSC Code', size=32, readonly=True),
        'employee_bank_no':
        fields.char('Employee Bank Account', required=True),
    }

    def init(self, cr):
        tools.drop_view_if_exists(cr, 'payment_advice_report')
        cr.execute("""
            create or replace view payment_advice_report as (
                select
                    min(l.id) as id,
                    sum(l.bysal) as bysal,
                    p.name,
                    p.state,
                    p.date,
                    p.number,
                    p.company_id,
                    p.bank_id,
                    p.chaque_nos as cheque_nos,
                    p.neft,
                    l.employee_id,
                    l.ifsc_code,
                    l.name as employee_bank_no,
                    to_char(p.date, 'YYYY') as year,
                    to_char(p.date, 'MM') as month,
                    to_char(p.date, 'YYYY-MM-DD') as day,
                    1 as nbr
                from
                    hr_payroll_advice as p
                    left join hr_payroll_advice_line as l on (p.id=l.advice_id)
                where 
                    l.employee_id IS NOT NULL
                group by
                    p.number,p.name,p.date,p.state,p.company_id,p.bank_id,p.chaque_nos,p.neft,
                    l.employee_id,l.advice_id,l.bysal,l.ifsc_code, l.name
            )
        """)
Beispiel #25
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 #26
0
class project_issue(osv.Model):
    _name = "project.issue"
    _description = "Project Issue"
    _order = "priority desc, create_date desc"
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _mail_post_access = 'read'

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

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

    def _read_group_stage_ids(self,
                              cr,
                              uid,
                              ids,
                              domain,
                              read_group_order=None,
                              access_rights_uid=None,
                              context=None):
        if context is None:
            context = {}
        access_rights_uid = access_rights_uid or uid
        stage_obj = self.pool.get('project.task.type')
        order = stage_obj._order
        # lame hack to allow reverting search, should just work in the trivial case
        if read_group_order == 'stage_id desc':
            order = "%s desc" % order
        # retrieve team_id from the context, add them to already fetched columns (ids)
        if 'default_project_id' in context:
            search_domain = [
                '|', ('project_ids', '=', context['default_project_id']),
                ('id', 'in', ids)
            ]
        else:
            search_domain = [('id', 'in', ids)]
        # perform search
        stage_ids = stage_obj._search(cr,
                                      uid,
                                      search_domain,
                                      order=order,
                                      access_rights_uid=access_rights_uid,
                                      context=context)
        result = stage_obj.name_get(cr,
                                    access_rights_uid,
                                    stage_ids,
                                    context=context)
        # restore order of the search
        result.sort(
            lambda x, y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))

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

    def _compute_day(self, cr, uid, ids, fields, args, context=None):
        """
        @param cr: the current row, from the database cursor,
        @param uid: the current user’s ID for security checks,
        @param ids: List of Openday’s IDs
        @return: difference between current date and log date
        @param context: A standard dictionary for contextual values
        """
        Calendar = self.pool['resource.calendar']

        res = dict((res_id, {}) for res_id in ids)
        for issue in self.browse(cr, uid, ids, context=context):
            values = {
                'day_open': 0.0,
                'day_close': 0.0,
                'working_hours_open': 0.0,
                'working_hours_close': 0.0,
                'days_since_creation': 0.0,
                'inactivity_days': 0.0,
            }
            # if the working hours on the project are not defined, use default ones (8 -> 12 and 13 -> 17 * 5), represented by None
            calendar_id = None
            if issue.project_id and issue.project_id.resource_calendar_id:
                calendar_id = issue.project_id.resource_calendar_id.id

            dt_create_date = datetime.strptime(issue.create_date,
                                               DEFAULT_SERVER_DATETIME_FORMAT)

            if issue.date_open:
                dt_date_open = datetime.strptime(
                    issue.date_open, DEFAULT_SERVER_DATETIME_FORMAT)
                values['day_open'] = (dt_date_open - dt_create_date
                                      ).total_seconds() / (24.0 * 3600)
                values['working_hours_open'] = Calendar._interval_hours_get(
                    cr,
                    uid,
                    calendar_id,
                    dt_create_date,
                    dt_date_open,
                    timezone_from_uid=issue.user_id.id or uid,
                    exclude_leaves=False,
                    context=context)

            if issue.date_closed:
                dt_date_closed = datetime.strptime(
                    issue.date_closed, DEFAULT_SERVER_DATETIME_FORMAT)
                values['day_close'] = (dt_date_closed - dt_create_date
                                       ).total_seconds() / (24.0 * 3600)
                values['working_hours_close'] = Calendar._interval_hours_get(
                    cr,
                    uid,
                    calendar_id,
                    dt_create_date,
                    dt_date_closed,
                    timezone_from_uid=issue.user_id.id or uid,
                    exclude_leaves=False,
                    context=context)

            days_since_creation = datetime.today() - dt_create_date
            values['days_since_creation'] = days_since_creation.days
            if issue.date_action_last:
                inactive_days = datetime.today() - datetime.strptime(
                    issue.date_action_last, DEFAULT_SERVER_DATETIME_FORMAT)
            elif issue.date_last_stage_update:
                inactive_days = datetime.today() - datetime.strptime(
                    issue.date_last_stage_update,
                    DEFAULT_SERVER_DATETIME_FORMAT)
            else:
                inactive_days = datetime.today() - datetime.strptime(
                    issue.create_date, DEFAULT_SERVER_DATETIME_FORMAT)
            values['inactivity_days'] = inactive_days.days

            # filter only required values
            for field in fields:
                res[issue.id][field] = values[field]

        return res

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

    _columns = {
        'id': fields.integer('ID', readonly=True),
        'name': fields.char('Issue', required=True),
        'active': fields.boolean('Active', required=False),
        'create_date': fields.datetime('Creation Date', readonly=True, select=True),
        'write_date': fields.datetime('Update Date', readonly=True),
        'days_since_creation': fields.function(_compute_day, string='Days since creation date', \
                                               multi='compute_day', type="integer", help="Difference in days between creation date and current date"),
        'date_deadline': fields.date('Deadline'),
        'team_id': fields.many2one('crm.team', 'Sales Team', oldname='section_id',\
                        select=True, help='Sales team to which Case belongs to.\
                             Define Responsible user and Email account for mail gateway.'                                                                                         ),
        'partner_id': fields.many2one('res.partner', 'Contact', select=1),
        'company_id': fields.many2one('res.company', 'Company'),
        'description': fields.text('Private Note'),
        'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State',
                                         track_visibility='onchange',
                                         help="A Issue's kanban state indicates special situations affecting it:\n"
                                              " * Normal is the default situation\n"
                                              " * Blocked indicates something is preventing the progress of this issue\n"
                                              " * Ready for next stage indicates the issue is ready to be pulled to the next stage",
                                         required=True),
        'email_from': fields.char('Email', size=128, help="These people will receive email.", select=1),
        'email_cc': fields.char('Watchers Emails', size=256, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
        'date_open': fields.datetime('Assigned', readonly=True, select=True),
        # Project Issue fields
        'date_closed': fields.datetime('Closed', readonly=True, select=True),
        'date': fields.datetime('Date'),
        'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
        'channel': fields.char('Channel', help="Communication channel."),
        'tag_ids': fields.many2many('project.tags', string='Tags'),
        'priority': fields.selection([('0','Low'), ('1','Normal'), ('2','High')], 'Priority', select=True),
        'stage_id': fields.many2one ('project.task.type', 'Stage',
                        track_visibility='onchange', select=True,
                        domain="[('project_ids', '=', project_id)]", copy=False),
        'project_id': fields.many2one('project.project', 'Project', track_visibility='onchange', select=True),
        'duration': fields.float('Duration'),
        'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]",
            help="You can link this issue to an existing task or directly create a new one from here"),
        'day_open': fields.function(_compute_day, string='Days to Assign',
                                    multi='compute_day', type="float",
                                    store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_open'], 10)}),
        'day_close': fields.function(_compute_day, string='Days to Close',
                                     multi='compute_day', type="float",
                                     store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_closed'], 10)}),
        'user_id': fields.many2one('res.users', 'Assigned to', required=False, select=1, track_visibility='onchange'),
        'working_hours_open': fields.function(_compute_day, string='Working Hours to assign the Issue',
                                              multi='compute_day', type="float",
                                              store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_open'], 10)}),
        'working_hours_close': fields.function(_compute_day, string='Working Hours to close the Issue',
                                               multi='compute_day', type="float",
                                               store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_closed'], 10)}),
        'inactivity_days': fields.function(_compute_day, string='Days since last action',
                                           multi='compute_day', type="integer", help="Difference in days between last action and current date"),
        'color': fields.integer('Color Index'),
        'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True),
        'date_action_last': fields.datetime('Last Action', readonly=1),
        'date_action_next': fields.datetime('Next Action', readonly=1),
        'legend_blocked': fields.related("stage_id", "legend_blocked", type="char", string='Kanban Blocked Explanation'),
        'legend_done': fields.related("stage_id", "legend_done", type="char", string='Kanban Valid Explanation'),
        'legend_normal': fields.related("stage_id", "legend_normal", type="char", string='Kanban Ongoing Explanation'),
    }

    _defaults = {
        'active':
        1,
        'team_id':
        lambda s, cr, uid, c: s.pool['crm.team']._get_default_team_id(
            cr, uid, context=c),
        'stage_id':
        lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
        'company_id':
        lambda s, cr, uid, c: s.pool['res.users']._get_company(
            cr, uid, context=c),
        'priority':
        '0',
        'kanban_state':
        'normal',
        'date_last_stage_update':
        fields.datetime.now,
        'user_id':
        lambda obj, cr, uid, context: uid,
    }

    _group_by_full = {'stage_id': _read_group_stage_ids}

    def copy(self, cr, uid, id, default=None, context=None):
        issue = self.read(cr, uid, [id], ['name'], context=context)[0]
        if not default:
            default = {}
        default = default.copy()
        default.update(name=_('%s (copy)') % (issue['name']))
        return super(project_issue, self).copy(cr,
                                               uid,
                                               id,
                                               default=default,
                                               context=context)

    def create(self, cr, uid, vals, context=None):
        context = dict(context or {})
        if vals.get('project_id') and not context.get('default_project_id'):
            context['default_project_id'] = vals.get('project_id')
        if vals.get('user_id') and not vals.get('date_open'):
            vals['date_open'] = fields.datetime.now()
        if 'stage_id' in vals:
            vals.update(
                self.onchange_stage_id(cr,
                                       uid,
                                       None,
                                       vals.get('stage_id'),
                                       context=context)['value'])

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

    def write(self, cr, uid, ids, vals, context=None):
        # stage change: update date_last_stage_update
        if 'stage_id' in vals:
            vals.update(
                self.onchange_stage_id(cr,
                                       uid,
                                       ids,
                                       vals.get('stage_id'),
                                       context=context)['value'])
            vals['date_last_stage_update'] = fields.datetime.now()
            if 'kanban_state' not in vals:
                vals['kanban_state'] = 'normal'
        # user_id change: update date_open
        if vals.get('user_id') and 'date_open' not in vals:
            vals['date_open'] = fields.datetime.now()

        return super(project_issue, self).write(cr, uid, ids, vals, context)

    def onchange_task_id(self, cr, uid, ids, task_id, context=None):
        if not task_id:
            return {'value': {}}
        task = self.pool.get('project.task').browse(cr,
                                                    uid,
                                                    task_id,
                                                    context=context)
        return {
            'value': {
                'user_id': task.user_id.id,
            }
        }

    def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
        """ This function returns value of partner email address based on partner
            :param part: Partner's id
        """
        if partner_id:
            partner = self.pool['res.partner'].browse(cr, uid, partner_id,
                                                      context)
            return {'value': {'email_from': partner.email}}
        return {'value': {'email_from': False}}

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

    # -------------------------------------------------------
    # Stage management
    # -------------------------------------------------------

    def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
        if not stage_id:
            return {'value': {}}
        stage = self.pool['project.task.type'].browse(cr,
                                                      uid,
                                                      stage_id,
                                                      context=context)
        if stage.fold:
            return {'value': {'date_closed': fields.datetime.now()}}
        return {'value': {'date_closed': False}}

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

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

    def _track_subtype(self, cr, uid, ids, init_values, context=None):
        record = self.browse(cr, uid, ids[0], context=context)
        if 'kanban_state' in init_values and record.kanban_state == 'blocked':
            return 'project_issue.mt_issue_blocked'
        elif 'kanban_state' in init_values and record.kanban_state == 'done':
            return 'project_issue.mt_issue_ready'
        elif 'user_id' in init_values and record.user_id:  # assigned -> new
            return 'project_issue.mt_issue_new'
        elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence <= 1:  # start stage -> new
            return 'project_issue.mt_issue_new'
        elif 'stage_id' in init_values:
            return 'project_issue.mt_issue_stage'
        return super(project_issue, self)._track_subtype(cr,
                                                         uid,
                                                         ids,
                                                         init_values,
                                                         context=context)

    def _notification_group_recipients(self,
                                       cr,
                                       uid,
                                       ids,
                                       message,
                                       recipients,
                                       done_ids,
                                       group_data,
                                       context=None):
        """ Override the mail.thread method to handle project users and officers
        recipients. Indeed those will have specific action in their notification
        emails: creating tasks, assigning it. """
        group_project_user = self.pool['ir.model.data'].xmlid_to_res_id(
            cr, uid, 'project.group_project_user')
        for recipient in recipients:
            if recipient.id in done_ids:
                continue
            if recipient.user_ids and group_project_user in recipient.user_ids[
                    0].groups_id.ids:
                group_data['group_project_user'] |= recipient
                done_ids.add(recipient.id)
        return super(project_issue,
                     self)._notification_group_recipients(cr,
                                                          uid,
                                                          ids,
                                                          message,
                                                          recipients,
                                                          done_ids,
                                                          group_data,
                                                          context=context)

    def _notification_get_recipient_groups(self,
                                           cr,
                                           uid,
                                           ids,
                                           message,
                                           recipients,
                                           context=None):
        res = super(project_issue,
                    self)._notification_get_recipient_groups(cr,
                                                             uid,
                                                             ids,
                                                             message,
                                                             recipients,
                                                             context=context)

        new_action_id = self.pool['ir.model.data'].xmlid_to_res_id(
            cr, uid, 'project_issue.project_issue_categ_act0')
        take_action = self._notification_link_helper(cr,
                                                     uid,
                                                     ids,
                                                     'assign',
                                                     context=context)
        new_action = self._notification_link_helper(cr,
                                                    uid,
                                                    ids,
                                                    'new',
                                                    context=context,
                                                    action_id=new_action_id)

        task_record = self.browse(cr, uid, ids[0], context=context)
        actions = []
        if not task_record.user_id:
            actions.append({'url': take_action, 'title': _('I take it')})
        else:
            actions.append({'url': new_action, 'title': _('New Issue')})

        res['group_project_user'] = {'actions': actions}
        return res

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

    def message_get_suggested_recipients(self, cr, uid, ids, context=None):
        recipients = super(project_issue,
                           self).message_get_suggested_recipients(
                               cr, uid, ids, context=context)
        try:
            for issue in self.browse(cr, uid, ids, context=context):
                if issue.partner_id:
                    issue._message_add_suggested_recipient(
                        recipients,
                        partner=issue.partner_id,
                        reason=_('Customer'))
                elif issue.email_from:
                    issue._message_add_suggested_recipient(
                        recipients,
                        email=issue.email_from,
                        reason=_('Customer Email'))
        except AccessError:  # no read access rights -> just ignore suggested recipients because this imply modifying followers
            pass
        return recipients

    def email_split(self, cr, uid, ids, msg, context=None):
        email_list = tools.email_split((msg.get('to') or '') + ',' +
                                       (msg.get('cc') or ''))
        # check left-part is not already an alias
        issue_ids = self.browse(cr, uid, ids, context=context)
        aliases = [
            issue.project_id.alias_name for issue in issue_ids
            if issue.project_id
        ]
        return filter(lambda x: x.split('@')[0] not in aliases, email_list)

    def message_new(self, cr, uid, msg, custom_values=None, context=None):
        """ Overrides mail_thread message_new that is called by the mailgateway
            through message_process.
            This override updates the document according to the email.
        """
        if custom_values is None:
            custom_values = {}
        context = dict(context or {}, state_to='draft')
        defaults = {
            'name': msg.get('subject') or _("No Subject"),
            'email_from': msg.get('from'),
            'email_cc': msg.get('cc'),
            'partner_id': msg.get('author_id', False),
            'user_id': False,
        }
        defaults.update(custom_values)

        res_id = super(project_issue, self).message_new(cr,
                                                        uid,
                                                        msg,
                                                        custom_values=defaults,
                                                        context=context)
        email_list = self.email_split(cr, uid, [res_id], msg, context=context)
        partner_ids = self._find_partner_from_emails(cr,
                                                     uid, [res_id],
                                                     email_list,
                                                     force_create=True,
                                                     context=context)
        self.message_subscribe(cr, uid, [res_id], partner_ids, context=context)
        return res_id

    def message_update(self,
                       cr,
                       uid,
                       ids,
                       msg,
                       update_vals=None,
                       context=None):
        """ Override to update the issue according to the email. """

        email_list = self.email_split(cr, uid, ids, msg, context=context)
        partner_ids = self._find_partner_from_emails(cr,
                                                     uid,
                                                     ids,
                                                     email_list,
                                                     force_create=True,
                                                     context=context)
        self.message_subscribe(cr, uid, ids, partner_ids, context=context)
        return super(project_issue,
                     self).message_update(cr,
                                          uid,
                                          ids,
                                          msg,
                                          update_vals=update_vals,
                                          context=context)

    @api.cr_uid_ids_context
    @api.returns('mail.message', lambda value: value.id)
    def message_post(self,
                     cr,
                     uid,
                     thread_id,
                     subtype=None,
                     context=None,
                     **kwargs):
        """ Overrides mail_thread message_post so that we can set the date of last action field when
            a new message is posted on the issue.
        """
        if context is None:
            context = {}
        res = super(project_issue, self).message_post(cr,
                                                      uid,
                                                      thread_id,
                                                      subtype=subtype,
                                                      context=context,
                                                      **kwargs)
        if thread_id and subtype:
            self.write(cr,
                       SUPERUSER_ID,
                       thread_id, {'date_action_last': fields.datetime.now()},
                       context=context)
        return res
Beispiel #27
0
class crm_opportunity_report(osv.Model):
    """ CRM Opportunity Analysis """
    _name = "crm.opportunity.report"
    _auto = False
    _description = "CRM Opportunity Analysis"
    _rec_name = 'date_deadline'
    _inherit = ["utm.mixin"]

    _columns = {
        'date_deadline':
        fields.date('Expected Closing', readonly=True),
        'create_date':
        fields.datetime('Creation Date', readonly=True),
        'opening_date':
        fields.datetime('Assignation Date', readonly=True),
        'date_closed':
        fields.datetime('Close Date', readonly=True),
        'date_last_stage_update':
        fields.datetime('Last Stage Update', readonly=True),
        'active':
        fields.boolean('Active', readonly=True),

        # durations
        'delay_open':
        fields.float('Delay to Assign',
                     digits=(16, 2),
                     readonly=True,
                     group_operator="avg",
                     help="Number of Days to open the case"),
        'delay_close':
        fields.float('Delay to Close',
                     digits=(16, 2),
                     readonly=True,
                     group_operator="avg",
                     help="Number of Days to close the case"),
        'delay_expected':
        fields.float('Overpassed Deadline',
                     digits=(16, 2),
                     readonly=True,
                     group_operator="avg"),
        'user_id':
        fields.many2one('res.users', 'User', readonly=True),
        'team_id':
        fields.many2one('crm.team',
                        'Sales Team',
                        oldname='section_id',
                        readonly=True),
        'nbr_activities':
        fields.integer('# of Activities', readonly=True),
        'country_id':
        fields.many2one('res.country', 'Country', readonly=True),
        'company_id':
        fields.many2one('res.company', 'Company', readonly=True),
        'probability':
        fields.float('Probability',
                     digits=(16, 2),
                     readonly=True,
                     group_operator="avg"),
        'total_revenue':
        fields.float('Total Revenue', digits=(16, 2), readonly=True),
        'expected_revenue':
        fields.float('Expected Revenue', digits=(16, 2), readonly=True),
        'stage_id':
        fields.many2one('crm.stage',
                        'Stage',
                        readonly=True,
                        domain="[('team_ids', '=', team_id)]"),
        'stage_name':
        fields.char('Stage Name', readonly=True),
        'partner_id':
        fields.many2one('res.partner', 'Partner', readonly=True),
        'company_id':
        fields.many2one('res.company', 'Company', readonly=True),
        'priority':
        fields.selection(crm_stage.AVAILABLE_PRIORITIES, 'Priority'),
        'type':
        fields.selection(
            [
                ('lead', 'Lead'),
                ('opportunity', 'Opportunity'),
            ],
            'Type',
            help="Type is used to separate Leads and Opportunities"),
        'lost_reason':
        fields.many2one('crm.lost.reason', 'Lost Reason', readonly=True),
        'date_conversion':
        fields.datetime('Conversion Date', readonly=True),
    }

    def init(self, cr):
        tools.drop_view_if_exists(cr, 'crm_opportunity_report')
        cr.execute("""
            CREATE OR REPLACE VIEW crm_opportunity_report AS (
                SELECT
                    c.id,
                    c.date_deadline,

                    c.date_open as opening_date,
                    c.date_closed as date_closed,
                    c.date_last_stage_update as date_last_stage_update,

                    c.user_id,
                    c.probability,
                    c.stage_id,
                    stage.name as stage_name,
                    c.type,
                    c.company_id,
                    c.priority,
                    c.team_id,
                    activity.nbr_activities,
                    c.active,
                    c.campaign_id,
                    c.source_id,
                    c.medium_id,
                    c.partner_id,
                    c.country_id,
                    c.planned_revenue as total_revenue,
                    c.planned_revenue*(c.probability/100) as expected_revenue,
                    c.create_date as create_date,
                    extract('epoch' from (c.date_closed-c.create_date))/(3600*24) as  delay_close,
                    abs(extract('epoch' from (c.date_deadline - c.date_closed))/(3600*24)) as  delay_expected,
                    extract('epoch' from (c.date_open-c.create_date))/(3600*24) as  delay_open,
                    c.lost_reason,
                    c.date_conversion as date_conversion
                FROM
                    "crm_lead" c
                LEFT JOIN (
                    SELECT m.res_id, COUNT(*) nbr_activities
                    FROM "mail_message" m
                    WHERE m.model = 'crm.lead'
                    GROUP BY m.res_id ) activity
                ON
                    (activity.res_id = c.id)
                LEFT JOIN "crm_stage" stage
                ON stage.id = c.stage_id
                GROUP BY c.id, activity.nbr_activities, stage.name
            )""")
Beispiel #28
0
class stock_warehouse(osv.osv):
    _inherit = 'stock.warehouse'
    _columns = {
        'manufacture_to_resupply':
        fields.boolean(
            'Manufacture in this Warehouse',
            help=
            "When products are manufactured, they can be manufactured in this warehouse."
        ),
        'manufacture_pull_id':
        fields.many2one('procurement.rule', 'Manufacture Rule'),
    }

    _defaults = {
        'manufacture_to_resupply': True,
    }

    def _get_manufacture_pull_rule(self, cr, uid, warehouse, context=None):
        route_obj = self.pool.get('stock.location.route')
        data_obj = self.pool.get('ir.model.data')
        try:
            manufacture_route_id = data_obj.get_object_reference(
                cr, uid, 'mrp', 'route_warehouse0_manufacture')[1]
        except:
            manufacture_route_id = route_obj.search(
                cr, uid, [('name', 'like', _('Manufacture'))], context=context)
            manufacture_route_id = manufacture_route_id and manufacture_route_id[
                0] or False
        if not manufacture_route_id:
            raise UserError(_('Can\'t find any generic Manufacture route.'))

        return {
            'name':
            self._format_routename(cr,
                                   uid,
                                   warehouse,
                                   _(' Manufacture'),
                                   context=context),
            'location_id':
            warehouse.lot_stock_id.id,
            'route_id':
            manufacture_route_id,
            'action':
            'manufacture',
            'picking_type_id':
            warehouse.int_type_id.id,
            'propagate':
            False,
            'warehouse_id':
            warehouse.id,
        }

    def create_routes(self, cr, uid, ids, warehouse, context=None):
        pull_obj = self.pool.get('procurement.rule')
        res = super(stock_warehouse, self).create_routes(cr,
                                                         uid,
                                                         ids,
                                                         warehouse,
                                                         context=context)
        if warehouse.manufacture_to_resupply:
            manufacture_pull_vals = self._get_manufacture_pull_rule(
                cr, uid, warehouse, context=context)
            manufacture_pull_id = pull_obj.create(cr,
                                                  uid,
                                                  manufacture_pull_vals,
                                                  context=context)
            res['manufacture_pull_id'] = manufacture_pull_id
        return res

    def write(self, cr, uid, ids, vals, context=None):
        pull_obj = self.pool.get('procurement.rule')
        if isinstance(ids, (int, long)):
            ids = [ids]

        if 'manufacture_to_resupply' in vals:
            if vals.get("manufacture_to_resupply"):
                for warehouse in self.browse(cr, uid, ids, context=context):
                    if not warehouse.manufacture_pull_id:
                        manufacture_pull_vals = self._get_manufacture_pull_rule(
                            cr, uid, warehouse, context=context)
                        manufacture_pull_id = pull_obj.create(
                            cr, uid, manufacture_pull_vals, context=context)
                        vals['manufacture_pull_id'] = manufacture_pull_id
            else:
                for warehouse in self.browse(cr, uid, ids, context=context):
                    if warehouse.manufacture_pull_id:
                        pull_obj.unlink(cr,
                                        uid,
                                        warehouse.manufacture_pull_id.id,
                                        context=context)
        return super(stock_warehouse, self).write(cr,
                                                  uid,
                                                  ids,
                                                  vals,
                                                  context=None)

    def get_all_routes_for_wh(self, cr, uid, warehouse, context=None):
        all_routes = super(stock_warehouse,
                           self).get_all_routes_for_wh(cr,
                                                       uid,
                                                       warehouse,
                                                       context=context)
        if warehouse.manufacture_to_resupply and warehouse.manufacture_pull_id and warehouse.manufacture_pull_id.route_id:
            all_routes += [warehouse.manufacture_pull_id.route_id.id]
        return all_routes

    def _handle_renaming(self, cr, uid, warehouse, name, code, context=None):
        res = super(stock_warehouse, self)._handle_renaming(cr,
                                                            uid,
                                                            warehouse,
                                                            name,
                                                            code,
                                                            context=context)
        pull_obj = self.pool.get('procurement.rule')
        #change the manufacture procurement rule name
        if warehouse.manufacture_pull_id:
            pull_obj.write(cr,
                           uid,
                           warehouse.manufacture_pull_id.id, {
                               'name':
                               warehouse.manufacture_pull_id.name.replace(
                                   warehouse.name, name, 1)
                           },
                           context=context)
        return res

    def _get_all_products_to_resupply(self, cr, uid, warehouse, context=None):
        res = super(stock_warehouse,
                    self)._get_all_products_to_resupply(cr,
                                                        uid,
                                                        warehouse,
                                                        context=context)
        if warehouse.manufacture_pull_id and warehouse.manufacture_pull_id.route_id:
            for product_id in res:
                for route in self.pool.get('product.product').browse(
                        cr, uid, product_id, context=context).route_ids:
                    if route.id == warehouse.manufacture_pull_id.route_id.id:
                        res.remove(product_id)
                        break
        return res
Beispiel #29
0
class mrp_repair_line(osv.osv, ProductChangeMixin):
    _name = 'mrp.repair.line'
    _description = 'Repair Line'

    def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
        """ Calculates amount.
        @param field_name: Name of field.
        @param arg: Argument
        @return: Dictionary of values.
        """
        res = {}
        tax_obj = self.pool.get('account.tax')
        # cur_obj = self.pool.get('res.currency')
        for line in self.browse(cr, uid, ids, context=context):
            if line.to_invoice:
                cur = line.repair_id.pricelist_id.currency_id
                taxes = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, cur.id, line.product_uom_qty, line.product_id.id, line.repair_id.partner_id.id)
                #res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
                res[line.id] = taxes['total_included']
            else:
                res[line.id] = 0
        return res

    _columns = {
        'name': fields.char('Description', required=True),
        'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference', ondelete='cascade', select=True),
        'type': fields.selection([('add', 'Add'), ('remove', 'Remove')], 'Type', required=True),
        'to_invoice': fields.boolean('To Invoice'),
        'product_id': fields.many2one('product.product', 'Product', required=True),
        'invoiced': fields.boolean('Invoiced', readonly=True, copy=False),
        'price_unit': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Product Price')),
        'price_subtotal': fields.function(_amount_line, string='Subtotal', digits=0),
        'tax_id': fields.many2many('account.tax', 'repair_operation_line_tax', 'repair_operation_line_id', 'tax_id', 'Taxes'),
        'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
        'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
        'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True, copy=False),
        'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True),
        'location_dest_id': fields.many2one('stock.location', 'Dest. Location', required=True, select=True),
        'move_id': fields.many2one('stock.move', 'Inventory Move', readonly=True, copy=False),
        'lot_id': fields.many2one('stock.production.lot', 'Lot'),
        'state': fields.selection([
                    ('draft', 'Draft'),
                    ('confirmed', 'Confirmed'),
                    ('done', 'Done'),
                    ('cancel', 'Cancelled')], 'Status', required=True, readonly=True, copy=False,
                    help=' * The \'Draft\' status is set automatically as draft when repair order in draft status. \
                        \n* The \'Confirmed\' status is set automatically as confirm when repair order in confirm status. \
                        \n* The \'Done\' status is set automatically when repair order is completed.\
                        \n* The \'Cancelled\' status is set automatically when user cancel repair order.'),
    }
    _defaults = {
        'state': lambda *a: 'draft',
        'product_uom_qty': lambda *a: 1,
    }

    def onchange_operation_type(self, cr, uid, ids, type, guarantee_limit, company_id=False, context=None):
        """ On change of operation type it sets source location, destination location
        and to invoice field.
        @param product: Changed operation type.
        @param guarantee_limit: Guarantee limit of current record.
        @return: Dictionary of values.
        """
        if not type:
            return {'value': {
                'location_id': False,
                'location_dest_id': False
                }}
        location_obj = self.pool.get('stock.location')
        warehouse_obj = self.pool.get('stock.warehouse')
        location_id = location_obj.search(cr, uid, [('usage', '=', 'production')], context=context)
        location_id = location_id and location_id[0] or False

        if type == 'add':
            # TOCHECK: Find stock location for user's company warehouse or
            # repair order's company's warehouse (company_id field is added in fix of lp:831583)
            args = company_id and [('company_id', '=', company_id)] or []
            warehouse_ids = warehouse_obj.search(cr, uid, args, context=context)
            stock_id = False
            if warehouse_ids:
                stock_id = warehouse_obj.browse(cr, uid, warehouse_ids[0], context=context).lot_stock_id.id
            to_invoice = (guarantee_limit and datetime.strptime(guarantee_limit, '%Y-%m-%d') < datetime.now())

            return {'value': {
                'to_invoice': to_invoice,
                'location_id': stock_id,
                'location_dest_id': location_id
                }}
        scrap_location_ids = location_obj.search(cr, uid, [('scrap_location', '=', True)], context=context)

        return {'value': {
                'to_invoice': False,
                'location_id': location_id,
                'location_dest_id': scrap_location_ids and scrap_location_ids[0] or False,
                }}
Beispiel #30
0
class AcquirerPaypal(osv.Model):
    _inherit = 'payment.acquirer'

    def _get_paypal_urls(self, cr, uid, environment, context=None):
        """ Paypal URLS """
        if environment == 'prod':
            return {
                'paypal_form_url': 'https://www.paypal.com/cgi-bin/webscr',
                'paypal_rest_url': 'https://api.paypal.com/v1/oauth2/token',
            }
        else:
            return {
                'paypal_form_url': 'https://www.sandbox.paypal.com/cgi-bin/webscr',
                'paypal_rest_url': 'https://api.sandbox.paypal.com/v1/oauth2/token',
            }

    def _get_providers(self, cr, uid, context=None):
        providers = super(AcquirerPaypal, self)._get_providers(cr, uid, context=context)
        providers.append(['paypal', 'Paypal'])
        return providers

    _columns = {
        'paypal_email_account': fields.char('Paypal Email ID', required_if_provider='paypal'),
        'paypal_seller_account': fields.char(
            'Paypal Merchant ID',
            help='The Merchant ID is used to ensure communications coming from Paypal are valid and secured.'),
        'paypal_use_ipn': fields.boolean('Use IPN', help='Paypal Instant Payment Notification'),
        # Server 2 server
        'paypal_api_enabled': fields.boolean('Use Rest API'),
        'paypal_api_username': fields.char('Rest API Username'),
        'paypal_api_password': fields.char('Rest API Password'),
        'paypal_api_access_token': fields.char('Access Token'),
        'paypal_api_access_token_validity': fields.datetime('Access Token Validity'),
    }

    _defaults = {
        'paypal_use_ipn': True,
        'fees_active': False,
        'fees_dom_fixed': 0.35,
        'fees_dom_var': 3.4,
        'fees_int_fixed': 0.35,
        'fees_int_var': 3.9,
        'paypal_api_enabled': False,
    }

    def _migrate_paypal_account(self, cr, uid, context=None):
        """ COMPLETE ME """
        cr.execute('SELECT id, paypal_account FROM res_company')
        res = cr.fetchall()
        for (company_id, company_paypal_account) in res:
            if company_paypal_account:
                company_paypal_ids = self.search(cr, uid, [('company_id', '=', company_id), ('provider', '=', 'paypal')], limit=1, context=context)
                if company_paypal_ids:
                    self.write(cr, uid, company_paypal_ids, {'paypal_email_account': company_paypal_account}, context=context)
                else:
                    paypal_view = self.pool['ir.model.data'].get_object(cr, uid, 'payment_paypal', 'paypal_acquirer_button')
                    self.create(cr, uid, {
                        'name': 'Paypal',
                        'provider': 'paypal',
                        'paypal_email_account': company_paypal_account,
                        'view_template_id': paypal_view.id,
                    }, context=context)
        return True

    def paypal_compute_fees(self, cr, uid, id, amount, currency_id, country_id, context=None):
        """ Compute paypal fees.

            :param float amount: the amount to pay
            :param integer country_id: an ID of a res.country, or None. This is
                                       the customer's country, to be compared to
                                       the acquirer company country.
            :return float fees: computed fees
        """
        acquirer = self.browse(cr, uid, id, context=context)
        if not acquirer.fees_active:
            return 0.0
        country = self.pool['res.country'].browse(cr, uid, country_id, context=context)
        if country and acquirer.company_id.country_id.id == country.id:
            percentage = acquirer.fees_dom_var
            fixed = acquirer.fees_dom_fixed
        else:
            percentage = acquirer.fees_int_var
            fixed = acquirer.fees_int_fixed
        fees = (percentage / 100.0 * amount + fixed ) / (1 - percentage / 100.0)
        return fees

    def paypal_form_generate_values(self, cr, uid, id, values, context=None):
        base_url = self.pool['ir.config_parameter'].get_param(cr, SUPERUSER_ID, 'web.base.url')
        acquirer = self.browse(cr, uid, id, context=context)

        paypal_tx_values = dict(values)
        paypal_tx_values.update({
            'cmd': '_xclick',
            'business': acquirer.paypal_email_account,
            'item_name': '%s: %s' % (acquirer.company_id.name, values['reference']),
            'item_number': values['reference'],
            'amount': values['amount'],
            'currency_code': values['currency'] and values['currency'].name or '',
            'address1': values.get('partner_address'),
            'city': values.get('partner_city'),
            'country': values.get('partner_country') and values.get('partner_country').name or '',
            'state': values.get('partner_state') and values.get('partner_state').name or '',
            'email': values.get('partner_email'),
            'zip_code': values.get('partner_zip'),
            'first_name': values.get('partner_first_name'),
            'last_name': values.get('partner_last_name'),
            'paypal_return': '%s' % urlparse.urljoin(base_url, PaypalController._return_url),
            'notify_url': '%s' % urlparse.urljoin(base_url, PaypalController._notify_url),
            'cancel_return': '%s' % urlparse.urljoin(base_url, PaypalController._cancel_url),
            'handling': '%.2f' % paypal_tx_values.pop('fees', 0.0) if acquirer.fees_active else False,
            'custom': json.dumps({'return_url': '%s' % paypal_tx_values.pop('return_url')}) if paypal_tx_values.get('return_url') else False,
        })
        return paypal_tx_values

    def paypal_get_form_action_url(self, cr, uid, id, context=None):
        acquirer = self.browse(cr, uid, id, context=context)
        return self._get_paypal_urls(cr, uid, acquirer.environment, context=context)['paypal_form_url']

    def _paypal_s2s_get_access_token(self, cr, uid, ids, context=None):
        """
        Note: see # see http://stackoverflow.com/questions/2407126/python-urllib2-basic-auth-problem
        for explanation why we use Authorization header instead of urllib2
        password manager
        """
        res = dict.fromkeys(ids, False)
        parameters = werkzeug.url_encode({'grant_type': 'client_credentials'})

        for acquirer in self.browse(cr, uid, ids, context=context):
            tx_url = self._get_paypal_urls(cr, uid, acquirer.environment)['paypal_rest_url']
            request = urllib2.Request(tx_url, parameters)

            # add other headers (https://developer.paypal.com/webapps/developer/docs/integration/direct/make-your-first-call/)
            request.add_header('Accept', 'application/json')
            request.add_header('Accept-Language', 'en_US')

            # add authorization header
            base64string = base64.encodestring('%s:%s' % (
                acquirer.paypal_api_username,
                acquirer.paypal_api_password)
            ).replace('\n', '')
            request.add_header("Authorization", "Basic %s" % base64string)

            request = urllib2.urlopen(request)
            result = request.read()
            res[acquirer.id] = json.loads(result).get('access_token')
            request.close()
        return res
Beispiel #31
0
# -*- coding: utf-8 -*-
from ecore.osv import orm, fields

def selection_fn(obj, cr, uid, context=None):
    return list(enumerate(["Corge", "Grault", "Wheee", "Moog"]))

def function_fn(model, cr, uid, ids, field_name, arg, context):
    return dict((id, 3) for id in ids)

def function_fn_write(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context):
    """ just so CreatorCase.export can be used
    """
    pass

models = [
    ('boolean', fields.boolean()),
    ('integer', fields.integer()),
    ('float', fields.float()),
    ('decimal', fields.float(digits=(16, 3))),
    ('string.bounded', fields.char('unknown', size=16)),
    ('string.required', fields.char('unknown', size=None, required=True)),
    ('string', fields.char('unknown', size=None)),
    ('date', fields.date()),
    ('datetime', fields.datetime()),
    ('text', fields.text()),
    ('selection', fields.selection([(1, "Foo"), (2, "Bar"), (3, "Qux"), (4, '')])),
    # here use size=-1 to store the values as integers instead of strings
    ('selection.function', fields.selection(selection_fn, size=-1)),
    # just relate to an integer
    ('many2one', fields.many2one('export.integer')),
    ('one2many', fields.one2many('export.one2many.child', 'parent_id')),