Beispiel #1
0
class ProductProduct(models.Model):
    _inherit = "product.product"

    date_from = fields.Date(compute='_compute_product_margin_fields_values',
                            string='Margin Date From')
    date_to = fields.Date(compute='_compute_product_margin_fields_values',
                          string='Margin Date To')
    invoice_state = fields.Selection(
        compute='_compute_product_margin_fields_values',
        selection=[('paid', 'Paid'), ('open_paid', 'Open and Paid'),
                   ('draft_open_paid', 'Draft, Open and Paid')],
        string='Invoice State',
        readonly=True)
    sale_avg_price = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='Avg. Sale Unit Price',
        help="Avg. Price in Customer Invoices.")
    purchase_avg_price = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='Avg. Purchase Unit Price',
        help="Avg. Price in Vendor Bills ")
    sale_num_invoiced = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='# Invoiced in Sale',
        help="Sum of Quantity in Customer Invoices")
    purchase_num_invoiced = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='# Invoiced in Purchase',
        help="Sum of Quantity in Vendor Bills")
    sales_gap = fields.Float(compute='_compute_product_margin_fields_values',
                             string='Sales Gap',
                             help="Expected Sale - Turn Over")
    purchase_gap = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='Purchase Gap',
        help="Normal Cost - Total Cost")
    turnover = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='Turnover',
        help=
        "Sum of Multiplication of Invoice price and quantity of Customer Invoices"
    )
    total_cost = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='Total Cost',
        help=
        "Sum of Multiplication of Invoice price and quantity of Vendor Bills ")
    sale_expected = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='Expected Sale',
        help=
        "Sum of Multiplication of Sale Catalog price and quantity of Customer Invoices"
    )
    normal_cost = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='Normal Cost',
        help="Sum of Multiplication of Cost price and quantity of Vendor Bills"
    )
    total_margin = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='Total Margin',
        help="Turnover - Standard price")
    expected_margin = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='Expected Margin',
        help="Expected Sale - Normal Cost")
    total_margin_rate = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='Total Margin Rate(%)',
        help="Total margin * 100 / Turnover")
    expected_margin_rate = fields.Float(
        compute='_compute_product_margin_fields_values',
        string='Expected Margin (%)',
        help="Expected margin * 100 / Expected Sale")

    @api.model
    def read_group(self,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   orderby=False,
                   lazy=True):
        """
            Inherit read_group to calculate the sum of the non-stored fields, as it is not automatically done anymore through the XML.
        """
        res = super(ProductProduct, self).read_group(domain,
                                                     fields,
                                                     groupby,
                                                     offset=offset,
                                                     limit=limit,
                                                     orderby=orderby,
                                                     lazy=lazy)
        fields_list = [
            'turnover', 'sale_avg_price', 'sale_purchase_price',
            'sale_num_invoiced', 'purchase_num_invoiced', 'sales_gap',
            'purchase_gap', 'total_cost', 'sale_expected', 'normal_cost',
            'total_margin', 'expected_margin', 'total_margin_rate',
            'expected_margin_rate'
        ]
        if any(x in fields for x in fields_list):
            # Calculate first for every product in which line it needs to be applied
            re_ind = 0
            prod_re = {}
            tot_products = self.browse([])
            for re in res:
                if re.get('__domain'):
                    products = self.search(re['__domain'])
                    tot_products |= products
                    for prod in products:
                        prod_re[prod.id] = re_ind
                re_ind += 1
            res_val = tot_products._compute_product_margin_fields_values(
                field_names=[x for x in fields if fields in fields_list])
            for key in res_val:
                for l in res_val[key]:
                    re = res[prod_re[key]]
                    if re.get(l):
                        re[l] += res_val[key][l]
                    else:
                        re[l] = res_val[key][l]
        return res

    def _compute_product_margin_fields_values(self, field_names=None):
        res = {}
        if field_names is None:
            field_names = []
        for val in self:
            res[val.id] = {}
            date_from = self.env.context.get('date_from',
                                             time.strftime('%Y-01-01'))
            date_to = self.env.context.get('date_to',
                                           time.strftime('%Y-12-31'))
            invoice_state = self.env.context.get('invoice_state', 'open_paid')
            res[val.id]['date_from'] = date_from
            res[val.id]['date_to'] = date_to
            res[val.id]['invoice_state'] = invoice_state
            invoice_types = ()
            states = ()
            if invoice_state == 'paid':
                states = ('paid', )
            elif invoice_state == 'open_paid':
                states = ('open', 'paid')
            elif invoice_state == 'draft_open_paid':
                states = ('draft', 'open', 'paid')
            if "force_company" in self.env.context:
                company_id = self.env.context['force_company']
            else:
                company_id = self.env.user.company_id.id

            #Cost price is calculated afterwards as it is a property
            sqlstr = """
                WITH currency_rate AS ({})
                select
                    sum(l.price_unit / (CASE COALESCE(cr.rate, 0) WHEN 0 THEN 1.0 ELSE cr.rate END) * l.quantity)/nullif(sum(l.quantity),0) as avg_unit_price,
                    sum(l.quantity) as num_qty,
                    sum(l.quantity * (l.price_subtotal_signed/(nullif(l.quantity,0)))) as total,
                    sum(l.quantity * pt.list_price) as sale_expected
                from account_invoice_line l
                left join account_invoice i on (l.invoice_id = i.id)
                left join product_product product on (product.id=l.product_id)
                left join product_template pt on (pt.id = product.product_tmpl_id)
                left join currency_rate cr on
                (cr.currency_id = i.currency_id and
                 cr.company_id = i.company_id and
                 cr.date_start <= COALESCE(i.date_invoice, NOW()) and
                 (cr.date_end IS NULL OR cr.date_end > COALESCE(i.date_invoice, NOW())))
                where l.product_id = %s and i.state in %s and i.type IN %s and (i.date_invoice IS NULL or (i.date_invoice>=%s and i.date_invoice<=%s and i.company_id=%s))
                """.format(self.env['res.currency']._select_companies_rates())
            invoice_types = ('out_invoice', 'in_refund')
            self.env.cr.execute(sqlstr, (val.id, states, invoice_types,
                                         date_from, date_to, company_id))
            result = self.env.cr.fetchall()[0]
            res[val.id]['sale_avg_price'] = result[0] and result[0] or 0.0
            res[val.id]['sale_num_invoiced'] = result[1] and result[1] or 0.0
            res[val.id]['turnover'] = result[2] and result[2] or 0.0
            res[val.id]['sale_expected'] = result[3] and result[3] or 0.0
            res[val.id]['sales_gap'] = res[val.id]['sale_expected'] - res[
                val.id]['turnover']
            ctx = self.env.context.copy()
            ctx['force_company'] = company_id
            invoice_types = ('in_invoice', 'out_refund')
            self.env.cr.execute(sqlstr, (val.id, states, invoice_types,
                                         date_from, date_to, company_id))
            result = self.env.cr.fetchall()[0]
            res[val.id]['purchase_avg_price'] = result[0] and result[0] or 0.0
            res[val.
                id]['purchase_num_invoiced'] = result[1] and result[1] or 0.0
            res[val.id]['total_cost'] = result[2] and result[2] or 0.0
            res[val.id]['normal_cost'] = val.standard_price * res[
                val.id]['purchase_num_invoiced']
            res[val.id]['purchase_gap'] = res[val.id]['normal_cost'] - res[
                val.id]['total_cost']

            res[val.id]['total_margin'] = res[val.id]['turnover'] - res[
                val.id]['total_cost']
            res[val.id]['expected_margin'] = res[
                val.id]['sale_expected'] - res[val.id]['normal_cost']
            res[val.id]['total_margin_rate'] = res[val.id]['turnover'] and res[
                val.id]['total_margin'] * 100 / res[val.id]['turnover'] or 0.0
            res[val.id]['expected_margin_rate'] = res[
                val.id]['sale_expected'] and res[
                    val.id]['expected_margin'] * 100 / res[
                        val.id]['sale_expected'] or 0.0
            for k, v in res[val.id].items():
                setattr(val, k, v)
        return res
Beispiel #2
0
class Repair(models.Model):
    _name = 'repair.order'
    _description = 'Repair Order'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _order = 'create_date desc'

    @api.model
    def _default_stock_location(self):
        warehouse = self.env['stock.warehouse'].search([], limit=1)
        if warehouse:
            return warehouse.lot_stock_id.id
        return False

    name = fields.Char('Repair Reference',
                       default=lambda self: self.env['ir.sequence'].
                       next_by_code('repair.order'),
                       copy=False,
                       required=True,
                       states={'confirmed': [('readonly', True)]})
    product_id = fields.Many2one('product.product',
                                 string='Product to Repair',
                                 readonly=True,
                                 required=True,
                                 states={'draft': [('readonly', False)]})
    product_qty = fields.Float(
        'Product Quantity',
        default=1.0,
        digits=dp.get_precision('Product Unit of Measure'),
        readonly=True,
        required=True,
        states={'draft': [('readonly', False)]})
    product_uom = fields.Many2one('uom.uom',
                                  'Product Unit of Measure',
                                  readonly=True,
                                  required=True,
                                  states={'draft': [('readonly', False)]})
    partner_id = fields.Many2one(
        'res.partner',
        'Customer',
        index=True,
        states={'confirmed': [('readonly', True)]},
        help=
        'Choose partner for whom the order will be invoiced and delivered. You can find a partner by its Name, TIN, Email or Internal Reference.'
    )
    address_id = fields.Many2one('res.partner',
                                 'Delivery Address',
                                 domain="[('parent_id','=',partner_id)]",
                                 states={'confirmed': [('readonly', True)]})
    default_address_id = fields.Many2one('res.partner',
                                         compute='_compute_default_address_id')
    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')],
        string='Status',
        copy=False,
        default='draft',
        readonly=True,
        track_visibility='onchange',
        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',
        'Location',
        default=_default_stock_location,
        index=True,
        readonly=True,
        required=True,
        help="This is the location where the product to repair is located.",
        states={
            'draft': [('readonly', False)],
            'confirmed': [('readonly', True)]
        })
    lot_id = fields.Many2one(
        'stock.production.lot',
        'Lot/Serial',
        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('repair.line',
                                 'repair_id',
                                 'Parts',
                                 copy=True,
                                 readonly=True,
                                 states={'draft': [('readonly', False)]})
    pricelist_id = fields.Many2one(
        'product.pricelist',
        'Pricelist',
        default=lambda self: self.env['product.pricelist'].search([], limit=1
                                                                  ).id,
        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")],
        string="Invoice Method",
        default='none',
        index=True,
        readonly=True,
        required=True,
        states={'draft': [('readonly', False)]},
        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',
                                 copy=False,
                                 readonly=True,
                                 track_visibility="onchange")
    move_id = fields.Many2one('stock.move',
                              'Move',
                              copy=False,
                              readonly=True,
                              track_visibility="onchange",
                              help="Move created by the repair order")
    fees_lines = fields.One2many('repair.fee',
                                 'repair_id',
                                 'Operations',
                                 copy=True,
                                 readonly=True,
                                 states={'draft': [('readonly', False)]})
    internal_notes = fields.Text('Internal Notes')
    quotation_notes = fields.Text('Quotation Notes')
    company_id = fields.Many2one('res.company',
                                 'Company',
                                 default=lambda self: self.env['res.company'].
                                 _company_default_get('repair.order'))
    invoiced = fields.Boolean('Invoiced', copy=False, readonly=True)
    repaired = fields.Boolean('Repaired', copy=False, readonly=True)
    amount_untaxed = fields.Float('Untaxed Amount',
                                  compute='_amount_untaxed',
                                  store=True)
    amount_tax = fields.Float('Taxes', compute='_amount_tax', store=True)
    amount_total = fields.Float('Total', compute='_amount_total', store=True)
    tracking = fields.Selection('Product Tracking',
                                related="product_id.tracking",
                                readonly=False)

    @api.one
    @api.depends('partner_id')
    def _compute_default_address_id(self):
        if self.partner_id:
            self.default_address_id = self.partner_id.address_get(
                ['contact'])['contact']

    @api.one
    @api.depends('operations.price_subtotal', 'invoice_method',
                 'fees_lines.price_subtotal', 'pricelist_id.currency_id')
    def _amount_untaxed(self):
        total = sum(operation.price_subtotal for operation in self.operations)
        total += sum(fee.price_subtotal for fee in self.fees_lines)
        self.amount_untaxed = self.pricelist_id.currency_id.round(total)

    @api.one
    @api.depends('operations.price_unit', 'operations.product_uom_qty',
                 'operations.product_id', 'fees_lines.price_unit',
                 'fees_lines.product_uom_qty', 'fees_lines.product_id',
                 'pricelist_id.currency_id', 'partner_id')
    def _amount_tax(self):
        val = 0.0
        for operation in self.operations:
            if operation.tax_id:
                tax_calculate = operation.tax_id.compute_all(
                    operation.price_unit, self.pricelist_id.currency_id,
                    operation.product_uom_qty, operation.product_id,
                    self.partner_id)
                for c in tax_calculate['taxes']:
                    val += c['amount']
        for fee in self.fees_lines:
            if fee.tax_id:
                tax_calculate = fee.tax_id.compute_all(
                    fee.price_unit, self.pricelist_id.currency_id,
                    fee.product_uom_qty, fee.product_id, self.partner_id)
                for c in tax_calculate['taxes']:
                    val += c['amount']
        self.amount_tax = val

    @api.one
    @api.depends('amount_untaxed', 'amount_tax')
    def _amount_total(self):
        self.amount_total = self.pricelist_id.currency_id.round(
            self.amount_untaxed + self.amount_tax)

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

    @api.onchange('product_id')
    def onchange_product_id(self):
        self.guarantee_limit = False
        self.lot_id = False
        if self.product_id:
            self.product_uom = self.product_id.uom_id.id

    @api.onchange('product_uom')
    def onchange_product_uom(self):
        res = {}
        if not self.product_id or not self.product_uom:
            return res
        if self.product_uom.category_id != self.product_id.uom_id.category_id:
            res['warning'] = {
                'title':
                _('Warning'),
                'message':
                _('The product unit of measure you chose has a different category than the product unit of measure.'
                  )
            }
            self.product_uom = self.product_id.uom_id.id
        return res

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        if not self.partner_id:
            self.address_id = False
            self.partner_invoice_id = False
            self.pricelist_id = self.env['product.pricelist'].search(
                [], limit=1).id
        else:
            addresses = self.partner_id.address_get(
                ['delivery', 'invoice', 'contact'])
            self.address_id = addresses['delivery'] or addresses['contact']
            self.partner_invoice_id = addresses['invoice']
            self.pricelist_id = self.partner_id.property_product_pricelist.id

    @api.multi
    def button_dummy(self):
        # TDE FIXME: this button is very interesting
        return True

    @api.multi
    def action_repair_cancel_draft(self):
        if self.filtered(lambda repair: repair.state != 'cancel'):
            raise UserError(
                _("Repair must be canceled in order to reset it to draft."))
        self.mapped('operations').write({'state': 'draft'})
        return self.write({'state': 'draft'})

    def action_validate(self):
        self.ensure_one()
        precision = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        available_qty_owner = self.env['stock.quant']._get_available_quantity(
            self.product_id,
            self.location_id,
            self.lot_id,
            owner_id=self.partner_id,
            strict=True)
        available_qty_noown = self.env['stock.quant']._get_available_quantity(
            self.product_id, self.location_id, self.lot_id, strict=True)
        for available_qty in [available_qty_owner, available_qty_noown]:
            if float_compare(available_qty,
                             self.product_qty,
                             precision_digits=precision) >= 0:
                return self.action_repair_confirm()
        else:
            return {
                'name':
                _('Insufficient Quantity'),
                'view_type':
                'form',
                'view_mode':
                'form',
                'res_model':
                'stock.warn.insufficient.qty.repair',
                'view_id':
                self.env.ref(
                    'repair.stock_warn_insufficient_qty_repair_form_view').id,
                'type':
                'ir.actions.act_window',
                'context': {
                    'default_product_id': self.product_id.id,
                    'default_location_id': self.location_id.id,
                    'default_repair_id': self.id
                },
                'target':
                'new'
            }

    @api.multi
    def action_repair_confirm(self):
        """ Repair order state is set to 'To be invoiced' when invoice method
        is 'Before repair' else state becomes 'Confirmed'.
        @param *arg: Arguments
        @return: True
        """
        if self.filtered(lambda repair: repair.state != 'draft'):
            raise UserError(_("Only draft repairs can be confirmed."))
        before_repair = self.filtered(
            lambda repair: repair.invoice_method == 'b4repair')
        before_repair.write({'state': '2binvoiced'})
        to_confirm = self - before_repair
        to_confirm_operations = to_confirm.mapped('operations')
        to_confirm_operations.write({'state': 'confirmed'})
        to_confirm.write({'state': 'confirmed'})
        return True

    @api.multi
    def action_repair_cancel(self):
        if self.filtered(lambda repair: repair.state == 'done'):
            raise UserError(_("Cannot cancel completed repairs."))
        if any(repair.invoiced for repair in self):
            raise UserError(_('The repair order is already invoiced.'))
        self.mapped('operations').write({'state': 'cancel'})
        return self.write({'state': 'cancel'})

    @api.multi
    def action_send_mail(self):
        self.ensure_one()
        template_id = self.env.ref('repair.mail_template_repair_quotation').id
        ctx = {
            'default_model': 'repair.order',
            'default_res_id': self.id,
            'default_use_template': bool(template_id),
            'default_template_id': template_id,
            'default_composition_mode': 'comment',
            'custom_layout': 'mail.mail_notification_light',
        }
        return {
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'mail.compose.message',
            'target': 'new',
            'context': ctx,
        }

    @api.multi
    def print_repair_order(self):
        return self.env.ref('repair.action_report_repair_order').report_action(
            self)

    def action_repair_invoice_create(self):
        for repair in self:
            repair.action_invoice_create()
            if repair.invoice_method == 'b4repair':
                repair.action_repair_ready()
            elif repair.invoice_method == 'after_repair':
                repair.write({'state': 'done'})
        return True

    @api.multi
    def action_invoice_create(self, group=False):
        """ Creates invoice(s) for repair order.
        @param group: It is set to true when group invoice is to be generated.
        @return: Invoice Ids.
        """
        res = dict.fromkeys(self.ids, False)
        invoices_group = {}
        InvoiceLine = self.env['account.invoice.line']
        Invoice = self.env['account.invoice']
        for repair in self.filtered(lambda repair: repair.state not in (
                'draft', 'cancel') and not repair.invoice_id):
            if not repair.partner_id.id and not repair.partner_invoice_id.id:
                raise UserError(
                    _('You have to select an 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:
                    invoice = invoices_group[repair.partner_invoice_id.id]
                    invoice.write({
                        '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 ''),
                    })
                else:
                    if not repair.partner_id.property_account_receivable_id:
                        raise UserError(
                            _('No account defined for partner "%s".') %
                            repair.partner_id.name)
                    fp_id = repair.partner_id.property_account_position_id.id or self.env[
                        'account.fiscal.position'].get_fiscal_position(
                            repair.partner_id.id,
                            delivery_id=repair.address_id.id)
                    invoice = Invoice.create({
                        'name':
                        repair.name,
                        'origin':
                        repair.name,
                        'type':
                        'out_invoice',
                        'account_id':
                        repair.partner_id.property_account_receivable_id.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':
                        fp_id
                    })
                    invoices_group[repair.partner_invoice_id.id] = invoice
                repair.write({'invoiced': True, 'invoice_id': invoice.id})

                for operation in repair.operations:
                    if operation.type == 'add':
                        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 = InvoiceLine.create({
                            'invoice_id':
                            invoice.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
                        })
                        operation.write({
                            'invoiced': True,
                            'invoice_line_id': invoice_line.id
                        })
                for fee in repair.fees_lines:
                    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_line = InvoiceLine.create({
                        'invoice_id':
                        invoice.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
                    })
                    fee.write({
                        'invoiced': True,
                        'invoice_line_id': invoice_line.id
                    })
                invoice.compute_taxes()
                res[repair.id] = invoice.id
        return res

    @api.multi
    def action_created_invoice(self):
        self.ensure_one()
        return {
            'name': _('Invoice created'),
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'account.invoice',
            'view_id': self.env.ref('account.invoice_form').id,
            'target': 'current',
            'res_id': self.invoice_id.id,
        }

    def action_repair_ready(self):
        self.mapped('operations').write({'state': 'confirmed'})
        return self.write({'state': 'ready'})

    @api.multi
    def action_repair_start(self):
        """ Writes repair order state to 'Under Repair'
        @return: True
        """
        if self.filtered(
                lambda repair: repair.state not in ['confirmed', 'ready']):
            raise UserError(
                _("Repair must be confirmed before starting reparation."))
        self.mapped('operations').write({'state': 'confirmed'})
        return self.write({'state': 'under_repair'})

    @api.multi
    def action_repair_end(self):
        """ Writes repair order state to 'To be invoiced' if invoice method is
        After repair else state is set to 'Ready'.
        @return: True
        """
        if self.filtered(lambda repair: repair.state != 'under_repair'):
            raise UserError(
                _("Repair must be under repair in order to end reparation."))
        for repair in self:
            repair.write({'repaired': True})
            vals = {'state': 'done'}
            vals['move_id'] = repair.action_repair_done().get(repair.id)
            if not repair.invoiced and repair.invoice_method == 'after_repair':
                vals['state'] = '2binvoiced'
            repair.write(vals)
        return True

    @api.multi
    def action_repair_done(self):
        """ Creates stock move for operation and stock move for final product of repair order.
        @return: Move ids of final products

        """
        if self.filtered(lambda repair: not repair.repaired):
            raise UserError(
                _("Repair must be repaired in order to make the product moves."
                  ))
        res = {}
        precision = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        Move = self.env['stock.move']
        for repair in self:
            # Try to create move with the appropriate owner
            owner_id = False
            available_qty_owner = self.env[
                'stock.quant']._get_available_quantity(
                    repair.product_id,
                    repair.location_id,
                    repair.lot_id,
                    owner_id=repair.partner_id,
                    strict=True)
            if float_compare(available_qty_owner,
                             repair.product_qty,
                             precision_digits=precision) >= 0:
                owner_id = repair.partner_id.id

            moves = self.env['stock.move']
            for operation in repair.operations:
                move = Move.create({
                    'name':
                    repair.name,
                    'product_id':
                    operation.product_id.id,
                    'product_uom_qty':
                    operation.product_uom_qty,
                    'product_uom':
                    operation.product_uom.id,
                    'partner_id':
                    repair.address_id.id,
                    'location_id':
                    operation.location_id.id,
                    'location_dest_id':
                    operation.location_dest_id.id,
                    'move_line_ids': [(
                        0,
                        0,
                        {
                            'product_id': operation.product_id.id,
                            'lot_id': operation.lot_id.id,
                            'product_uom_qty': 0,  # bypass reservation here
                            'product_uom_id': operation.product_uom.id,
                            'qty_done': operation.product_uom_qty,
                            'package_id': False,
                            'result_package_id': False,
                            'owner_id': owner_id,
                            'location_id':
                            operation.location_id.id,  #TODO: owner stuff
                            'location_dest_id': operation.location_dest_id.id,
                        })],
                    'repair_id':
                    repair.id,
                    'origin':
                    repair.name,
                })
                moves |= move
                operation.write({'move_id': move.id, 'state': 'done'})
            move = Move.create({
                '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.id,
                'location_id':
                repair.location_id.id,
                'location_dest_id':
                repair.location_id.id,
                'move_line_ids': [(
                    0,
                    0,
                    {
                        'product_id':
                        repair.product_id.id,
                        'lot_id':
                        repair.lot_id.id,
                        'product_uom_qty':
                        0,  # bypass reservation here
                        'product_uom_id':
                        repair.product_uom.id or repair.product_id.uom_id.id,
                        'qty_done':
                        repair.product_qty,
                        'package_id':
                        False,
                        'result_package_id':
                        False,
                        'owner_id':
                        owner_id,
                        'location_id':
                        repair.location_id.id,  #TODO: owner stuff
                        'location_dest_id':
                        repair.location_id.id,
                    })],
                'repair_id':
                repair.id,
                'origin':
                repair.name,
            })
            consumed_lines = moves.mapped('move_line_ids')
            produced_lines = move.move_line_ids
            moves |= move
            moves._action_done()
            produced_lines.write(
                {'consume_line_ids': [(6, 0, consumed_lines.ids)]})
            res[repair.id] = move.id
        return res
Beispiel #3
0
class Challenge(models.Model):
    _inherit = 'gamification.challenge'

    category = fields.Selection(selection_add=[('forum', 'Website / Forum')])
Beispiel #4
0
class AcquirerPaypal(models.Model):
    _inherit = 'payment.acquirer'

    provider = fields.Selection(selection_add=[('paypal', 'Paypal')])
    paypal_email_account = fields.Char('Paypal Email ID',
                                       required_if_provider='paypal',
                                       groups='base.group_user')
    paypal_seller_account = fields.Char(
        'Paypal Merchant ID',
        groups='base.group_user',
        help=
        'The Merchant ID is used to ensure communications coming from Paypal are valid and secured.'
    )
    paypal_use_ipn = fields.Boolean('Use IPN',
                                    default=True,
                                    help='Paypal Instant Payment Notification',
                                    groups='base.group_user')
    paypal_pdt_token = fields.Char(
        string='Paypal PDT Token',
        help=
        'Payment Data Transfer allows you to receive notification of successful payments as they are made.',
        groups='base.group_user')
    # Server 2 server
    paypal_api_enabled = fields.Boolean('Use Rest API', default=False)
    paypal_api_username = fields.Char('Rest API Username',
                                      groups='base.group_user')
    paypal_api_password = fields.Char('Rest API Password',
                                      groups='base.group_user')
    paypal_api_access_token = fields.Char('Access Token',
                                          groups='base.group_user')
    paypal_api_access_token_validity = fields.Datetime(
        'Access Token Validity', groups='base.group_user')
    # Default paypal fees
    fees_dom_fixed = fields.Float(default=0.35)
    fees_dom_var = fields.Float(default=3.4)
    fees_int_fixed = fields.Float(default=0.35)
    fees_int_var = fields.Float(default=3.9)

    def _get_feature_support(self):
        """Get advanced feature support by provider.

        Each provider should add its technical in the corresponding
        key for the following features:
            * fees: support payment fees computations
            * authorize: support authorizing payment (separates
                         authorization and capture)
            * tokenize: support saving payment data in a payment.tokenize
                        object
        """
        res = super(AcquirerPaypal, self)._get_feature_support()
        res['fees'].append('paypal')
        return res

    @api.model
    def _get_paypal_urls(self, environment):
        """ 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',
            }

    @api.multi
    def paypal_compute_fees(self, amount, currency_id, country_id):
        """ 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
        """
        if not self.fees_active:
            return 0.0
        country = self.env['res.country'].browse(country_id)
        if country and self.company_id.country_id.id == country.id:
            percentage = self.fees_dom_var
            fixed = self.fees_dom_fixed
        else:
            percentage = self.fees_int_var
            fixed = self.fees_int_fixed
        fees = (percentage / 100.0 * amount + fixed) / (1 - percentage / 100.0)
        return fees

    @api.multi
    def paypal_form_generate_values(self, values):
        base_url = self.get_base_url()

        paypal_tx_values = dict(values)
        paypal_tx_values.update({
            'cmd':
            '_xclick',
            'business':
            self.paypal_email_account,
            'item_name':
            '%s: %s' % (self.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').code or '',
            'state':
            values.get('partner_state') and
            (values.get('partner_state').code
             or 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':
            urls.url_join(base_url, PaypalController._return_url),
            'notify_url':
            urls.url_join(base_url, PaypalController._notify_url),
            'cancel_return':
            urls.url_join(base_url, PaypalController._cancel_url),
            'handling':
            '%.2f' %
            paypal_tx_values.pop('fees', 0.0) if self.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

    @api.multi
    def paypal_get_form_action_url(self):
        return self._get_paypal_urls(self.environment)['paypal_form_url']
Beispiel #5
0
class AcquirerSips(models.Model):
    _inherit = 'payment.acquirer'

    provider = fields.Selection(selection_add=[('sips', 'Sips')])
    sips_merchant_id = fields.Char('Merchant ID',
                                   help="Used for production only",
                                   required_if_provider='sips',
                                   groups='base.group_user')
    sips_secret = fields.Char('Secret Key',
                              size=64,
                              required_if_provider='sips',
                              groups='base.group_user')
    sips_test_url = fields.Char(
        "Test url",
        required_if_provider='sips',
        default='https://payment-webinit.simu.sips-atos.com/paymentInit')
    sips_prod_url = fields.Char(
        "Production url",
        required_if_provider='sips',
        default='https://payment-webinit.sips-atos.com/paymentInit')
    sips_version = fields.Char("Interface Version",
                               required_if_provider='sips',
                               default='HP_2.3')

    def _sips_generate_shasign(self, values):
        """ Generate the shasign for incoming or outgoing communications.
        :param dict values: transaction values
        :return string: shasign
        """
        if self.provider != 'sips':
            raise ValidationError(_('Incorrect payment acquirer provider'))
        data = values['Data']

        # Test key provided by Worldine
        key = u'002001000000001_KEY1'

        if self.environment == 'prod':
            key = getattr(self, 'sips_secret')

        shasign = sha256((data + key).encode('utf-8'))
        return shasign.hexdigest()

    @api.multi
    def sips_form_generate_values(self, values):
        self.ensure_one()
        base_url = self.get_base_url()
        currency = self.env['res.currency'].sudo().browse(
            values['currency_id'])
        currency_code = CURRENCY_CODES.get(currency.name, False)
        if not currency_code:
            raise ValidationError(_('Currency not supported by Wordline'))
        amount = round(values['amount'] * 100)
        if self.environment == 'prod':
            # For production environment, key version 2 is required
            merchant_id = getattr(self, 'sips_merchant_id')
            key_version = self.env['ir.config_parameter'].sudo().get_param(
                'sips.key_version', '2')
        else:
            # Test key provided by Atos Wordline works only with version 1
            merchant_id = '002001000000001'
            key_version = '1'

        sips_tx_values = dict(values)
        sips_tx_values.update({
            'Data':
            u'amount=%s|' % amount + u'currencyCode=%s|' % currency_code +
            u'merchantId=%s|' % merchant_id + u'normalReturnUrl=%s|' %
            urls.url_join(base_url, SipsController._return_url) +
            u'automaticResponseUrl=%s|' %
            urls.url_join(base_url, SipsController._notify_url) +
            u'transactionReference=%s|' % values['reference'] +
            u'statementReference=%s|' % values['reference'] +
            u'keyVersion=%s' % key_version,
            'InterfaceVersion':
            self.sips_version,
        })

        return_context = {}
        if sips_tx_values.get('return_url'):
            return_context[u'return_url'] = u'%s' % urls.url_quote(
                sips_tx_values.pop('return_url'))
        return_context[u'reference'] = u'%s' % sips_tx_values['reference']
        sips_tx_values['Data'] += u'|returnContext=%s' % (
            json.dumps(return_context))

        shasign = self._sips_generate_shasign(sips_tx_values)
        sips_tx_values['Seal'] = shasign
        return sips_tx_values

    @api.multi
    def sips_get_form_action_url(self):
        self.ensure_one()
        return self.environment == 'prod' and self.sips_prod_url or self.sips_test_url
Beispiel #6
0
class PosOrderReport(models.Model):
    _name = "report.pos.order"
    _description = "Point of Sale Orders Report"
    _auto = False
    _order = 'date desc'

    date = fields.Datetime(string='Order Date', readonly=True)
    order_id = fields.Many2one('pos.order', string='Order', readonly=True)
    partner_id = fields.Many2one('res.partner',
                                 string='Customer',
                                 readonly=True)
    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 readonly=True)
    product_tmpl_id = fields.Many2one('product.template',
                                      string='Product Template',
                                      readonly=True)
    state = fields.Selection([('draft', 'New'), ('paid', 'Paid'),
                              ('done', 'Posted'), ('invoiced', 'Invoiced'),
                              ('cancel', 'Cancelled')],
                             string='Status')
    user_id = fields.Many2one('res.users', string='Salesperson', readonly=True)
    price_total = fields.Float(string='Total Price', readonly=True)
    price_sub_total = fields.Float(string='Subtotal w/o discount',
                                   readonly=True)
    total_discount = fields.Float(string='Total Discount', readonly=True)
    average_price = fields.Float(string='Average Price',
                                 readonly=True,
                                 group_operator="avg")
    location_id = fields.Many2one('stock.location',
                                  string='Location',
                                  readonly=True)
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True)
    nbr_lines = fields.Integer(string='Sale Line Count',
                               readonly=True,
                               oldname='nbr')
    product_qty = fields.Integer(string='Product Quantity', readonly=True)
    journal_id = fields.Many2one('account.journal', string='Journal')
    delay_validation = fields.Integer(string='Delay Validation')
    product_categ_id = fields.Many2one('product.category',
                                       string='Product Category',
                                       readonly=True)
    invoiced = fields.Boolean(readonly=True)
    config_id = fields.Many2one('pos.config',
                                string='Point of Sale',
                                readonly=True)
    pos_categ_id = fields.Many2one('pos.category',
                                   string='PoS Category',
                                   readonly=True)
    pricelist_id = fields.Many2one('product.pricelist',
                                   string='Pricelist',
                                   readonly=True)
    session_id = fields.Many2one('pos.session',
                                 string='Session',
                                 readonly=True)

    def _select(self):
        return """
            SELECT
                MIN(l.id) AS id,
                COUNT(*) AS nbr_lines,
                s.date_order AS date,
                SUM(l.qty) AS product_qty,
                SUM(l.qty * l.price_unit) AS price_sub_total,
                SUM((l.qty * l.price_unit) * (100 - l.discount) / 100) AS price_total,
                SUM((l.qty * l.price_unit) * (l.discount / 100)) AS total_discount,
                (SUM(l.qty*l.price_unit)/SUM(l.qty * u.factor))::decimal AS average_price,
                SUM(cast(to_char(date_trunc('day',s.date_order) - date_trunc('day',s.create_date),'DD') AS INT)) AS delay_validation,
                s.id as order_id,
                s.partner_id AS partner_id,
                s.state AS state,
                s.user_id AS user_id,
                s.location_id AS location_id,
                s.company_id AS company_id,
                s.sale_journal AS journal_id,
                l.product_id AS product_id,
                pt.categ_id AS product_categ_id,
                p.product_tmpl_id,
                ps.config_id,
                pt.pos_categ_id,
                s.pricelist_id,
                s.session_id,
                s.invoice_id IS NOT NULL AS invoiced
        """

    def _from(self):
        return """
            FROM pos_order_line AS l
                LEFT JOIN pos_order s ON (s.id=l.order_id)
                LEFT JOIN product_product p ON (l.product_id=p.id)
                LEFT JOIN product_template pt ON (p.product_tmpl_id=pt.id)
                LEFT JOIN uom_uom u ON (u.id=pt.uom_id)
                LEFT JOIN pos_session ps ON (s.session_id=ps.id)
        """

    def _group_by(self):
        return """
            GROUP BY
                s.id, s.date_order, s.partner_id,s.state, pt.categ_id,
                s.user_id, s.location_id, s.company_id, s.sale_journal,
                s.pricelist_id, s.invoice_id, s.create_date, s.session_id,
                l.product_id,
                pt.categ_id, pt.pos_categ_id,
                p.product_tmpl_id,
                ps.config_id
        """

    def _having(self):
        return """
            HAVING
                SUM(l.qty * u.factor) != 0
        """

    @api.model_cr
    def init(self):
        tools.drop_view_if_exists(self._cr, self._table)
        self._cr.execute("""
            CREATE OR REPLACE VIEW %s AS (
                %s
                %s
                %s
                %s
            )
        """ % (self._table, self._select(), self._from(), self._group_by(),
               self._having()))
Beispiel #7
0
class FinancialYearOpeningWizard(models.TransientModel):
    _name = 'account.financial.year.op'
    _description = 'Opening Balance of Financial Year'

    company_id = fields.Many2one(comodel_name='res.company', required=True)
    opening_move_posted = fields.Boolean(
        string='Opening Move Posted', compute='_compute_opening_move_posted')
    opening_date = fields.Date(
        string='Opening Date',
        required=True,
        related='company_id.account_opening_date',
        help=
        "Date from which the accounting is managed in Swerp. It is the date of the opening entry.",
        readonly=False)
    fiscalyear_last_day = fields.Integer(
        related="company_id.fiscalyear_last_day",
        required=True,
        readonly=False,
        help="Fiscal year last day.")
    fiscalyear_last_month = fields.Selection(
        selection=[(1, 'January'), (2, 'February'), (3, 'March'), (4, 'April'),
                   (5, 'May'), (6, 'June'), (7, 'July'), (8, 'August'),
                   (9, 'September'), (10, 'October'), (11, 'November'),
                   (12, 'December')],
        related="company_id.fiscalyear_last_month",
        readonly=False,
        required=True,
        help="Fiscal year last month.")

    @api.depends('company_id.account_opening_move_id')
    def _compute_opening_move_posted(self):
        for record in self:
            record.opening_move_posted = record.company_id.opening_move_posted(
            )

    @api.multi
    def write(self, vals):
        # Amazing workaround: non-stored related fields on company are a BAD idea since the 3 fields
        # must follow the constraint '_check_fiscalyear_last_day'. The thing is, in case of related
        # fields, the inverse write is done one value at a time, and thus the constraint is verified
        # one value at a time... so it is likely to fail.
        for wiz in self:
            wiz.company_id.write({
                'account_opening_date':
                vals.get('opening_date')
                or wiz.company_id.account_opening_date,
                'fiscalyear_last_day':
                vals.get('fiscalyear_last_day')
                or wiz.company_id.fiscalyear_last_day,
                'fiscalyear_last_month':
                vals.get('fiscalyear_last_month')
                or wiz.company_id.fiscalyear_last_month,
            })
        vals.pop('opening_date', None)
        vals.pop('fiscalyear_last_day', None)
        vals.pop('fiscalyear_last_month', None)
        return super().write(vals)

    @api.multi
    def action_save_onboarding_fiscal_year(self):
        self.env.user.company_id.set_onboarding_step_done(
            'account_setup_fy_data_state')
Beispiel #8
0
class Property(models.Model):
    _name = 'ir.property'
    _description = 'Company Property'

    name = fields.Char(index=True)
    res_id = fields.Char(
        string='Resource',
        index=True,
        help="If not set, acts as a default value for new resources",
    )
    company_id = fields.Many2one('res.company', string='Company', index=True)
    fields_id = fields.Many2one('ir.model.fields',
                                string='Field',
                                ondelete='cascade',
                                required=True,
                                index=True)
    value_float = fields.Float()
    value_integer = fields.Integer()
    value_text = fields.Text()  # will contain (char, text)
    value_binary = fields.Binary()
    value_reference = fields.Char()
    value_datetime = fields.Datetime()
    type = fields.Selection([
        ('char', 'Char'),
        ('float', 'Float'),
        ('boolean', 'Boolean'),
        ('integer', 'Integer'),
        ('text', 'Text'),
        ('binary', 'Binary'),
        ('many2one', 'Many2One'),
        ('date', 'Date'),
        ('datetime', 'DateTime'),
        ('selection', 'Selection'),
    ],
                            required=True,
                            default='many2one',
                            index=True)

    @api.multi
    def _update_values(self, values):
        if 'value' not in values:
            return values
        value = values.pop('value')

        prop = None
        type_ = values.get('type')
        if not type_:
            if self:
                prop = self[0]
                type_ = prop.type
            else:
                type_ = self._fields['type'].default(self)

        field = TYPE2FIELD.get(type_)
        if not field:
            raise UserError(_('Invalid type'))

        if field == 'value_reference':
            if not value:
                value = False
            elif isinstance(value, models.BaseModel):
                value = '%s,%d' % (value._name, value.id)
            elif isinstance(value, pycompat.integer_types):
                field_id = values.get('fields_id')
                if not field_id:
                    if not prop:
                        raise ValueError()
                    field_id = prop.fields_id
                else:
                    field_id = self.env['ir.model.fields'].browse(field_id)

                value = '%s,%d' % (field_id.sudo().relation, value)

        values[field] = value
        return values

    @api.multi
    def write(self, values):
        # if any of the records we're writing on has a res_id=False *or*
        # we're writing a res_id=False on any record
        default_set = False
        if self._ids:
            self.env.cr.execute(
                'SELECT EXISTS (SELECT 1 FROM ir_property WHERE id in %s AND res_id IS NULL)',
                [self._ids])
            default_set = self.env.cr.rowcount == 1 or any(
                v.get('res_id') is False for v in values)
        r = super(Property, self).write(self._update_values(values))
        if default_set:
            self.clear_caches()
        return r

    @api.model_create_multi
    def create(self, vals_list):
        vals_list = [self._update_values(vals) for vals in vals_list]
        created_default = any(not v.get('res_id') for v in vals_list)
        r = super(Property, self).create(vals_list)
        if created_default:
            self.clear_caches()
        return r

    @api.multi
    def unlink(self):
        default_deleted = False
        if self._ids:
            self.env.cr.execute(
                'SELECT EXISTS (SELECT 1 FROM ir_property WHERE id in %s)',
                [self._ids])
            default_deleted = self.env.cr.rowcount == 1
        r = super().unlink()
        if default_deleted:
            self.clear_caches()
        return r

    @api.multi
    def get_by_record(self):
        self.ensure_one()
        if self.type in ('char', 'text', 'selection'):
            return self.value_text
        elif self.type == 'float':
            return self.value_float
        elif self.type == 'boolean':
            return bool(self.value_integer)
        elif self.type == 'integer':
            return self.value_integer
        elif self.type == 'binary':
            return self.value_binary
        elif self.type == 'many2one':
            if not self.value_reference:
                return False
            model, resource_id = self.value_reference.split(',')
            return self.env[model].browse(int(resource_id)).exists()
        elif self.type == 'datetime':
            return self.value_datetime
        elif self.type == 'date':
            if not self.value_datetime:
                return False
            return fields.Date.to_string(
                fields.Datetime.from_string(self.value_datetime))
        return False

    @api.model
    def get(self, name, model, res_id=False):
        if not res_id:
            t, v = self._get_default_property(name, model)
            if not v or t != 'many2one':
                return v
            return self.env[v[0]].browse(v[1])

        p = self._get_property(name, model, res_id=res_id)
        if p:
            return p.get_by_record()
        return False

    # only cache Property.get(res_id=False) as that's
    # sub-optimally, we can only call _company_default_get without a field
    # unless we want to create a more complete helper which does the
    # returning-a-company-id-from-a-model-and-name
    COMPANY_KEY = "self.env.context.get('force_company') or self.env['res.company']._company_default_get(model).id"

    @ormcache(COMPANY_KEY, 'name', 'model')
    def _get_default_property(self, name, model):
        prop = self._get_property(name, model, res_id=False)
        if not prop:
            return None, False
        v = prop.get_by_record()
        if prop.type != 'many2one':
            return prop.type, v
        return 'many2one', v and (v._name, v.id)

    def _get_property(self, name, model, res_id):
        domain = self._get_domain(name, model)
        if domain is not None:
            domain = [('res_id', '=', res_id)] + domain
            #make the search with company_id asc to make sure that properties specific to a company are given first
            return self.search(domain, limit=1, order='company_id')
        return self.browse(())

    def _get_domain(self, prop_name, model):
        field_id = self.env['ir.model.fields']._get_id(model, prop_name)
        if not field_id:
            return None
        company_id = self._context.get(
            'force_company') or self.env['res.company']._company_default_get(
                model, field_id).id
        return [('fields_id', '=', field_id),
                ('company_id', 'in', [company_id, False])]

    @api.model
    def get_multi(self, name, model, ids):
        """ Read the property field `name` for the records of model `model` with
            the given `ids`, and return a dictionary mapping `ids` to their
            corresponding value.
        """
        if not ids:
            return {}

        field = self.env[model]._fields[name]
        field_id = self.env['ir.model.fields']._get_id(model, name)
        company_id = (self._context.get('force_company')
                      or self.env['res.company']._company_default_get(
                          model, field_id).id)

        if field.type == 'many2one':
            comodel = self.env[field.comodel_name]
            model_pos = len(model) + 2
            value_pos = len(comodel._name) + 2
            # retrieve values: both p.res_id and p.value_reference are formatted
            # as "<rec._name>,<rec.id>"; the purpose of the LEFT JOIN is to
            # return the value id if it exists, NULL otherwise
            query = """
                SELECT substr(p.res_id, %s)::integer, r.id
                FROM ir_property p
                LEFT JOIN {} r ON substr(p.value_reference, %s)::integer=r.id
                WHERE p.fields_id=%s
                    AND (p.company_id=%s OR p.company_id IS NULL)
                    AND (p.res_id IN %s OR p.res_id IS NULL)
                ORDER BY p.company_id NULLS FIRST
            """.format(comodel._table)
            params = [model_pos, value_pos, field_id, company_id]
            clean = comodel.browse

        elif field.type in TYPE2FIELD:
            model_pos = len(model) + 2
            # retrieve values: p.res_id is formatted as "<rec._name>,<rec.id>"
            query = """
                SELECT substr(p.res_id, %s)::integer, p.{}
                FROM ir_property p
                WHERE p.fields_id=%s
                    AND (p.company_id=%s OR p.company_id IS NULL)
                    AND (p.res_id IN %s OR p.res_id IS NULL)
                ORDER BY p.company_id NULLS FIRST
            """.format(TYPE2FIELD[field.type])
            params = [model_pos, field_id, company_id]
            clean = TYPE2CLEAN[field.type]

        else:
            return dict.fromkeys(ids, False)

        # retrieve values
        cr = self.env.cr
        result = {}
        refs = {"%s,%s" % (model, id) for id in ids}
        for sub_refs in cr.split_for_in_conditions(refs):
            cr.execute(query, params + [sub_refs])
            result.update(cr.fetchall())

        # remove default value, add missing values, and format them
        default = result.pop(None, None)
        for id in ids:
            result[id] = clean(result.get(id, default))
        return result

    @api.model
    def set_multi(self, name, model, values, default_value=None):
        """ Assign the property field `name` for the records of model `model`
            with `values` (dictionary mapping record ids to their value).
            If the value for a given record is the same as the default
            value, the property entry will not be stored, to avoid bloating
            the database.
            If `default_value` is provided, that value will be used instead
            of the computed default value, to determine whether the value
            for a record should be stored or not.
        """
        def clean(value):
            return value.id if isinstance(value, models.BaseModel) else value

        if not values:
            return

        if default_value is None:
            domain = self._get_domain(name, model)
            if domain is None:
                raise Exception()
            # retrieve the default value for the field
            default_value = clean(self.get(name, model))

        # retrieve the properties corresponding to the given record ids
        field_id = self.env['ir.model.fields']._get_id(model, name)
        company_id = self.env.context.get(
            'force_company') or self.env['res.company']._company_default_get(
                model, field_id).id
        refs = {('%s,%s' % (model, id)): id for id in values}
        props = self.search([
            ('fields_id', '=', field_id),
            ('company_id', '=', company_id),
            ('res_id', 'in', list(refs)),
        ])

        # modify existing properties
        for prop in props:
            id = refs.pop(prop.res_id)
            value = clean(values[id])
            if value == default_value:
                # avoid prop.unlink(), as it clears the record cache that can
                # contain the value of other properties to set on record!
                prop.check_access_rights('unlink')
                prop.check_access_rule('unlink')
                self._cr.execute("DELETE FROM ir_property WHERE id=%s",
                                 [prop.id])
            elif value != clean(prop.get_by_record()):
                prop.write({'value': value})

        # create new properties for records that do not have one yet
        vals_list = []
        for ref, id in refs.items():
            value = clean(values[id])
            if value != default_value:
                vals_list.append({
                    'fields_id': field_id,
                    'company_id': company_id,
                    'res_id': ref,
                    'name': name,
                    'value': value,
                    'type': self.env[model]._fields[name].type,
                })
        self.create(vals_list)

    @api.model
    def search_multi(self, name, model, operator, value):
        """ Return a domain for the records that match the given condition. """
        default_matches = False
        include_zero = False

        field = self.env[model]._fields[name]
        if field.type == 'many2one':
            comodel = field.comodel_name

            def makeref(value):
                return value and '%s,%s' % (comodel, value)

            if operator == "=":
                value = makeref(value)
                # if searching properties not set, search those not in those set
                if value is False:
                    default_matches = True
            elif operator in ('!=', '<=', '<', '>', '>='):
                value = makeref(value)
            elif operator in ('in', 'not in'):
                value = [makeref(v) for v in value]
            elif operator in ('=like', '=ilike', 'like', 'not like', 'ilike',
                              'not ilike'):
                # most probably inefficient... but correct
                target = self.env[comodel]
                target_names = target.name_search(value,
                                                  operator=operator,
                                                  limit=None)
                target_ids = [n[0] for n in target_names]
                operator, value = 'in', [makeref(v) for v in target_ids]
        elif field.type in ('integer', 'float'):
            # No record is created in ir.property if the field's type is float or integer with a value
            # equal to 0. Then to match with the records that are linked to a property field equal to 0,
            # the negation of the operator must be taken  to compute the goods and the domain returned
            # to match the searched records is just the opposite.
            if value == 0 and operator == '=':
                operator = '!='
                include_zero = True
            elif value <= 0 and operator == '>=':
                operator = '<'
                include_zero = True
            elif value < 0 and operator == '>':
                operator = '<='
                include_zero = True
            elif value >= 0 and operator == '<=':
                operator = '>'
                include_zero = True
            elif value > 0 and operator == '<':
                operator = '>='
                include_zero = True

        # retrieve the properties that match the condition
        domain = self._get_domain(name, model)
        if domain is None:
            raise Exception()
        props = self.search(domain +
                            [(TYPE2FIELD[field.type], operator, value)])

        # retrieve the records corresponding to the properties that match
        good_ids = []
        for prop in props:
            if prop.res_id:
                res_model, res_id = prop.res_id.split(',')
                good_ids.append(int(res_id))
            else:
                default_matches = True

        if include_zero:
            return [('id', 'not in', good_ids)]
        elif default_matches:
            # exclude all records with a property that does not match
            all_ids = []
            props = self.search(domain + [('res_id', '!=', False)])
            for prop in props:
                res_model, res_id = prop.res_id.split(',')
                all_ids.append(int(res_id))
            bad_ids = list(set(all_ids) - set(good_ids))
            return [('id', 'not in', bad_ids)]
        else:
            return [('id', 'in', good_ids)]
Beispiel #9
0
class HrPayrollAdvice(models.Model):
    '''
    Bank Advice
    '''
    _name = 'hr.payroll.advice'
    _description = "Indian HR Payroll Advice"

    def _get_default_date(self):
        return fields.Date.from_string(fields.Date.today())

    name = fields.Char(readonly=True, required=True, states={'draft': [('readonly', False)]})
    note = fields.Text(string='Description', default='Please make the payroll transfer from above account number to the below mentioned account numbers towards employee salaries:')
    date = fields.Date(readonly=True, required=True, states={'draft': [('readonly', False)]}, default=_get_default_date,
        help='Advice Date is used to search Payslips')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('confirm', 'Confirmed'),
        ('cancel', 'Cancelled'),
    ], string='Status', default='draft', index=True, readonly=True)
    number = fields.Char(string='Reference', readonly=True)
    line_ids = fields.One2many('hr.payroll.advice.line', 'advice_id', string='Employee Salary',
        states={'draft': [('readonly', False)]}, readonly=True, copy=True)
    chaque_nos = fields.Char(string='Cheque Numbers')
    neft = fields.Boolean(string='NEFT Transaction', help='Check this box if your company use online transfer for salary')
    company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True,
        states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id)
    bank_id = fields.Many2one('res.bank', string='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', string='Batch', readonly=True)

    @api.multi
    def compute_advice(self):
        """
        Advice - Create Advice lines in Payment Advice and
        compute Advice lines.
        """
        for advice in self:
            old_lines = self.env['hr.payroll.advice.line'].search([('advice_id', '=', advice.id)])
            if old_lines:
                old_lines.unlink()
            payslips = self.env['hr.payslip'].search([('date_from', '<=', advice.date), ('date_to', '>=', advice.date), ('state', '=', 'done')])
            for slip in payslips:
                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,))
                payslip_line = self.env['hr.payslip.line'].search([('slip_id', '=', slip.id), ('code', '=', 'NET')], limit=1)
                if payslip_line:
                    self.env['hr.payroll.advice.line'].create({
                        'advice_id': advice.id,
                        'name': slip.employee_id.bank_account_id.acc_number,
                        'ifsc_code': slip.employee_id.bank_account_id.bank_bic or '',
                        'employee_id': slip.employee_id.id,
                        'bysal': payslip_line.total
                    })
                slip.advice_id = advice.id

    @api.multi
    def confirm_sheet(self):
        """
        confirm Advice - confirmed Advice after computing Advice Lines..
        """
        for advice in self:
            if not advice.line_ids:
                raise UserError(_('You can not confirm Payment advice without advice lines.'))
            date = fields.Date.from_string(fields.Date.today())
            advice_year = date.strftime('%m') + '-' + date.strftime('%Y')
            number = self.env['ir.sequence'].next_by_code('payment.advice')
            advice.write({
                'number': 'PAY' + '/' + advice_year + '/' + number,
                'state': 'confirm',
            })

    @api.multi
    def set_to_draft(self):
        """Resets Advice as draft.
        """
        self.write({'state': 'draft'})

    @api.multi
    def cancel_sheet(self):
        """Marks Advice as cancelled.
        """
        self.write({'state': 'cancel'})

    @api.onchange('company_id')
    def _onchange_company_id(self):
        self.bank_id = self.company_id.partner_id.bank_ids and self.company_id.partner_id.bank_ids[0].bank_id.id or False
Beispiel #10
0
class Company(models.Model):
    _name = "res.company"
    _description = 'Companies'
    _order = 'sequence, name'

    @api.multi
    def copy(self, default=None):
        raise UserError(
            _('Duplicating a company is not allowed. Please create a new company instead.'
              ))

    def _get_logo(self):
        return base64.b64encode(
            open(
                os.path.join(tools.config['root_path'], 'addons', 'base',
                             'static', 'img', 'res_company_logo.png'),
                'rb').read())

    @api.model
    def _get_euro(self):
        return self.env['res.currency.rate'].search([('rate', '=', 1)],
                                                    limit=1).currency_id

    @api.model
    def _get_user_currency(self):
        currency_id = self.env['res.users'].browse(
            self._uid).company_id.currency_id
        return currency_id or self._get_euro()

    name = fields.Char(related='partner_id.name',
                       string='Company Name',
                       required=True,
                       store=True,
                       readonly=False)
    sequence = fields.Integer(
        help='Used to order Companies in the company switcher', default=10)
    parent_id = fields.Many2one('res.company',
                                string='Parent Company',
                                index=True)
    child_ids = fields.One2many('res.company',
                                'parent_id',
                                string='Child Companies')
    partner_id = fields.Many2one('res.partner',
                                 string='Partner',
                                 required=True)
    report_header = fields.Text(
        string='Company Tagline',
        help=
        "Appears by default on the top right corner of your printed documents (report header)."
    )
    report_footer = fields.Text(
        string='Report Footer',
        translate=True,
        help="Footer text displayed at the bottom of all reports.")
    logo = fields.Binary(related='partner_id.image',
                         default=_get_logo,
                         string="Company Logo",
                         readonly=False)
    # 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.Binary(compute='_compute_logo_web', store=True)
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        required=True,
        default=lambda self: self._get_user_currency())
    user_ids = fields.Many2many('res.users',
                                'res_company_users_rel',
                                'cid',
                                'user_id',
                                string='Accepted Users')
    account_no = fields.Char(string='Account No.')
    street = fields.Char(compute='_compute_address', inverse='_inverse_street')
    street2 = fields.Char(compute='_compute_address',
                          inverse='_inverse_street2')
    zip = fields.Char(compute='_compute_address', inverse='_inverse_zip')
    city = fields.Char(compute='_compute_address', inverse='_inverse_city')
    state_id = fields.Many2one('res.country.state',
                               compute='_compute_address',
                               inverse='_inverse_state',
                               string="Fed. State")
    bank_ids = fields.One2many('res.partner.bank',
                               'company_id',
                               string='Bank Accounts',
                               help='Bank accounts related to this company')
    country_id = fields.Many2one('res.country',
                                 compute='_compute_address',
                                 inverse='_inverse_country',
                                 string="Country")
    email = fields.Char(related='partner_id.email', store=True, readonly=False)
    phone = fields.Char(related='partner_id.phone', store=True, readonly=False)
    website = fields.Char(related='partner_id.website', readonly=False)
    vat = fields.Char(related='partner_id.vat',
                      string="Tax ID",
                      readonly=False)
    company_registry = fields.Char()
    paperformat_id = fields.Many2one(
        'report.paperformat',
        'Paper format',
        default=lambda self: self.env.ref('base.paperformat_euro',
                                          raise_if_not_found=False))
    external_report_layout_id = fields.Many2one('ir.ui.view',
                                                'Document Template')
    _sql_constraints = [('name_uniq', 'unique (name)',
                         'The company name must be unique !')]

    base_onboarding_company_state = fields.Selection(
        [('not_done', "Not done"), ('just_done', "Just done"),
         ('done', "Done")],
        string="State of the onboarding company step",
        default='not_done')

    @api.model_cr
    def init(self):
        for company in self.search([('paperformat_id', '=', False)]):
            paperformat_euro = self.env.ref('base.paperformat_euro', False)
            if paperformat_euro:
                company.write({'paperformat_id': paperformat_euro.id})
        sup = super(Company, self)
        if hasattr(sup, 'init'):
            sup.init()

    def _get_company_address_fields(self, partner):
        return {
            'street': partner.street,
            'street2': partner.street2,
            'city': partner.city,
            'zip': partner.zip,
            'state_id': partner.state_id,
            'country_id': partner.country_id,
        }

    # TODO @api.depends(): currently now way to formulate the dependency on the
    # partner's contact address
    def _compute_address(self):
        for company in self.filtered(lambda company: company.partner_id):
            address_data = company.partner_id.sudo().address_get(
                adr_pref=['contact'])
            if address_data['contact']:
                partner = company.partner_id.browse(
                    address_data['contact']).sudo()
                company.update(company._get_company_address_fields(partner))

    def _inverse_street(self):
        for company in self:
            company.partner_id.street = company.street

    def _inverse_street2(self):
        for company in self:
            company.partner_id.street2 = company.street2

    def _inverse_zip(self):
        for company in self:
            company.partner_id.zip = company.zip

    def _inverse_city(self):
        for company in self:
            company.partner_id.city = company.city

    def _inverse_state(self):
        for company in self:
            company.partner_id.state_id = company.state_id

    def _inverse_country(self):
        for company in self:
            company.partner_id.country_id = company.country_id

    @api.depends('partner_id', 'partner_id.image')
    def _compute_logo_web(self):
        for company in self:
            company.logo_web = tools.image_resize_image(
                company.partner_id.image, (180, None))

    @api.onchange('state_id')
    def _onchange_state(self):
        if self.state_id.country_id:
            self.country_id = self.state_id.country_id

    @api.multi
    def on_change_country(self, country_id):
        # This function is called from account/models/chart_template.py, hence decorated with `multi`.
        self.ensure_one()
        currency_id = self._get_user_currency()
        if country_id:
            currency_id = self.env['res.country'].browse(
                country_id).currency_id
        return {'value': {'currency_id': currency_id.id}}

    @api.onchange('country_id')
    def _onchange_country_id_wrapper(self):
        res = {'domain': {'state_id': []}}
        if self.country_id:
            res['domain']['state_id'] = [('country_id', '=',
                                          self.country_id.id)]
        values = self.on_change_country(self.country_id.id)['value']
        for fname, value in values.items():
            setattr(self, fname, value)
        return res

    @api.model
    def _name_search(self,
                     name,
                     args=None,
                     operator='ilike',
                     limit=100,
                     name_get_uid=None):
        context = dict(self.env.context)
        newself = self
        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.
            companies = self.env.user.company_id + self.env.user.company_ids
            args = (args or []) + [('id', 'in', companies.ids)]
            newself = newself.sudo()
        return super(Company, newself.with_context(context))._name_search(
            name=name,
            args=args,
            operator=operator,
            limit=limit,
            name_get_uid=name_get_uid)

    @api.model
    @api.returns('self', lambda value: value.id)
    def _company_default_get(self, object=False, field=False):
        """ Returns the default company (usually the user's company).
        The 'object' and 'field' arguments are ignored but left here for
        backward compatibility and potential override.
        """
        return self.env['res.users']._get_company()

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

    @api.multi
    def _get_partner_hierarchy(self):
        self.ensure_one()
        parent = self.parent_id
        if parent:
            return parent._get_partner_hierarchy()
        else:
            return self._get_partner_descendance([])

    @api.multi
    def _get_partner_descendance(self, descendance):
        self.ensure_one()
        descendance.append(self.partner_id.id)
        for child_id in self._get_company_children(self.id):
            if child_id != self.id:
                descendance = self.browse(child_id)._get_partner_descendance(
                    descendance)
        return descendance

    # deprecated, use clear_caches() instead
    def cache_restart(self):
        self.clear_caches()

    @api.model
    def create(self, vals):
        if not vals.get('name') or vals.get('partner_id'):
            self.clear_caches()
            return super(Company, self).create(vals)
        partner = self.env['res.partner'].create({
            'name': vals['name'],
            'is_company': True,
            'image': vals.get('logo'),
            'customer': False,
            'email': vals.get('email'),
            'phone': vals.get('phone'),
            'website': vals.get('website'),
            'vat': vals.get('vat'),
        })
        vals['partner_id'] = partner.id
        self.clear_caches()
        company = super(Company, self).create(vals)
        # The write is made on the user to set it automatically in the multi company group.
        self.env.user.write({'company_ids': [(4, company.id)]})
        partner.write({'company_id': company.id})

        # Make sure that the selected currency is enabled
        if vals.get('currency_id'):
            currency = self.env['res.currency'].browse(vals['currency_id'])
            if not currency.active:
                currency.write({'active': True})
        return company

    @api.multi
    def write(self, values):
        self.clear_caches()
        # Make sure that the selected currency is enabled
        if values.get('currency_id'):
            currency = self.env['res.currency'].browse(values['currency_id'])
            if not currency.active:
                currency.write({'active': True})

        return super(Company, self).write(values)

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

    @api.multi
    def open_company_edit_report(self):
        self.ensure_one()
        return self.env['res.config.settings'].open_company()

    @api.multi
    def write_company_and_print_report(self, values):
        res = self.write(values)

        report_name = values.get('default_report_name')
        active_ids = values.get('active_ids')
        active_model = values.get('active_model')
        if report_name and active_ids and active_model:
            docids = self.env[active_model].browse(active_ids)
            return (self.env['ir.actions.report'].search(
                [('report_name', '=', report_name)],
                limit=1).with_context(values).report_action(docids))
        else:
            return res

    @api.model
    def action_open_base_onboarding_company(self):
        """ Onboarding step for company basic information. """
        action = self.env.ref(
            'base.action_open_base_onboarding_company').read()[0]
        action['res_id'] = self.env.user.company_id.id
        return action

    def set_onboarding_step_done(self, step_name):
        if self[step_name] == 'not_done':
            self[step_name] = 'just_done'

    def get_and_update_onbarding_state(self, onboarding_state, steps_states):
        """ Needed to display onboarding animations only one time. """
        old_values = {}
        all_done = True
        for step_state in steps_states:
            old_values[step_state] = self[step_state]
            if self[step_state] == 'just_done':
                self[step_state] = 'done'
            all_done = all_done and self[step_state] == 'done'

        if all_done:
            if self[onboarding_state] == 'not_done':
                # string `onboarding_state` instead of variable name is not an error
                old_values['onboarding_state'] = 'just_done'
            else:
                old_values['onboarding_state'] = 'done'
            self[onboarding_state] = 'done'
        return old_values

    @api.multi
    def action_save_onboarding_company_step(self):
        if bool(self.street):
            self.set_onboarding_step_done('base_onboarding_company_state')

    @api.model
    def _get_main_company(self):
        try:
            main_company = self.sudo().env.ref('base.main_company')
        except ValueError:
            main_company = self.env['res.company'].sudo().search([],
                                                                 limit=1,
                                                                 order="id")

        return main_company
Beispiel #11
0
class BarcodeNomenclature(models.Model):
    _name = 'barcode.nomenclature'
    _description = 'Barcode Nomenclature'

    name = fields.Char(string='Barcode Nomenclature', size=32, required=True, help='An internal identification of the barcode nomenclature')
    rule_ids = fields.One2many('barcode.rule', 'barcode_nomenclature_id', string='Rules', help='The list of barcode rules')
    upc_ean_conv = fields.Selection(UPC_EAN_CONVERSIONS, string='UPC/EAN Conversion', required=True, default='always',
        help="UPC Codes can be converted to EAN by prefixing them with a zero. This setting determines if a UPC/EAN barcode should be automatically converted in one way or another when trying to match a rule with the other encoding.")

    # returns the checksum of the ean13, or -1 if the ean has not the correct length, ean must be a string
    def ean_checksum(self, ean):
        code = list(ean)
        if len(code) != 13:
            return -1

        oddsum = evensum = total = 0
        code = code[:-1] # Remove checksum
        for i in range(len(code)):
            if i % 2 == 0:
                evensum += int(code[i])
            else:
                oddsum += int(code[i])
        total = oddsum * 3 + evensum
        return int((10 - total % 10) % 10)

    # returns the checksum of the ean8, or -1 if the ean has not the correct length, ean must be a string
    def ean8_checksum(self,ean):
        code = list(ean)
        if len(code) != 8:
            return -1

        sum1  = int(ean[1]) + int(ean[3]) + int(ean[5])
        sum2  = int(ean[0]) + int(ean[2]) + int(ean[4]) + int(ean[6])
        total = sum1 + 3 * sum2
        return int((10 - total % 10) % 10)

    # returns true if the barcode is a valid EAN barcode
    def check_ean(self, ean):
       return re.match("^\d+$", ean) and self.ean_checksum(ean) == int(ean[-1])

    # returns true if the barcode string is encoded with the provided encoding.
    def check_encoding(self, barcode, encoding):
        if encoding == 'ean13':
            return len(barcode) == 13 and re.match("^\d+$", barcode) and self.ean_checksum(barcode) == int(barcode[-1]) 
        elif encoding == 'ean8':
            return len(barcode) == 8 and re.match("^\d+$", barcode) and self.ean8_checksum(barcode) == int(barcode[-1])
        elif encoding == 'upca':
            return len(barcode) == 12 and re.match("^\d+$", barcode) and self.ean_checksum("0"+barcode) == int(barcode[-1])
        elif encoding == 'any':
            return True
        else:
            return False


    # Returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string.
    def sanitize_ean(self, ean):
        ean = ean[0:13]
        ean = ean + (13-len(ean))*'0'
        return ean[0:12] + str(self.ean_checksum(ean))

    # Returns a valid zero padded UPC-A from a UPC-A prefix. the UPC-A prefix must be a string.
    def sanitize_upc(self, upc):
        return self.sanitize_ean('0'+upc)[1:]

    # Checks if barcode matches the pattern
    # Additionaly retrieves the optional numerical content in barcode
    # Returns an object containing:
    # - value: the numerical value encoded in the barcode (0 if no value encoded)
    # - base_code: the barcode in which numerical content is replaced by 0's
    # - match: boolean
    def match_pattern(self, barcode, pattern):
        match = {
            "value": 0,
            "base_code": barcode,
            "match": False,
        }

        barcode = barcode.replace("\\", "\\\\").replace("{", '\{').replace("}", "\}").replace(".", "\.")
        numerical_content = re.search("[{][N]*[D]*[}]", pattern) # look for numerical content in pattern

        if numerical_content: # the pattern encodes a numerical content
            num_start = numerical_content.start() # start index of numerical content
            num_end = numerical_content.end() # end index of numerical content
            value_string = barcode[num_start:num_end-2] # numerical content in barcode

            whole_part_match = re.search("[{][N]*[D}]", numerical_content.group()) # looks for whole part of numerical content
            decimal_part_match = re.search("[{N][D]*[}]", numerical_content.group()) # looks for decimal part
            whole_part = value_string[:whole_part_match.end()-2] # retrieve whole part of numerical content in barcode
            decimal_part = "0." + value_string[decimal_part_match.start():decimal_part_match.end()-1] # retrieve decimal part
            if whole_part == '':
                whole_part = '0'
            match['value'] = int(whole_part) + float(decimal_part)

            match['base_code'] = barcode[:num_start] + (num_end-num_start-2)*"0" + barcode[num_end-2:] # replace numerical content by 0's in barcode
            match['base_code'] = match['base_code'].replace("\\\\", "\\").replace("\{", "{").replace("\}","}").replace("\.",".")
            pattern = pattern[:num_start] + (num_end-num_start-2)*"0" + pattern[num_end:] # replace numerical content by 0's in pattern to match

        match['match'] = re.match(pattern, match['base_code'][:len(pattern)])

        return match

    # Attempts to interpret an barcode (string encoding a barcode)
    # It will return an object containing various information about the barcode.
    # most importantly : 
    #  - code    : the barcode
    #  - type   : the type of the barcode: 
    #  - value  : if the id encodes a numerical value, it will be put there
    #  - base_code : the barcode code with all the encoding parts set to zero; the one put on
    #                the product in the backend
    def parse_barcode(self, barcode):
        parsed_result = {
            'encoding': '', 
            'type': 'error', 
            'code': barcode, 
            'base_code': barcode, 
            'value': 0,
        }

        rules = []
        for rule in self.rule_ids:
            rules.append({'type': rule.type, 'encoding': rule.encoding, 'sequence': rule.sequence, 'pattern': rule.pattern, 'alias': rule.alias})

        for rule in rules:
            cur_barcode = barcode
            if rule['encoding'] == 'ean13' and self.check_encoding(barcode,'upca') and self.upc_ean_conv in ['upc2ean','always']:
                cur_barcode = '0'+cur_barcode
            elif rule['encoding'] == 'upca' and self.check_encoding(barcode,'ean13') and barcode[0] == '0' and self.upc_ean_conv in ['ean2upc','always']:
                cur_barcode = cur_barcode[1:]

            if not self.check_encoding(barcode,rule['encoding']):
                continue

            match = self.match_pattern(cur_barcode, rule['pattern'])
            if match['match']:
                if rule['type'] == 'alias':
                    barcode = rule['alias']
                    parsed_result['code'] = barcode
                else:
                    parsed_result['encoding'] = rule['encoding']
                    parsed_result['type'] = rule['type']
                    parsed_result['value'] = match['value']
                    parsed_result['code'] = cur_barcode
                    if rule['encoding'] == "ean13":
                        parsed_result['base_code'] = self.sanitize_ean(match['base_code'])
                    elif rule['encoding'] == "upca":
                        parsed_result['base_code'] = self.sanitize_upc(match['base_code'])
                    else:
                        parsed_result['base_code'] = match['base_code']
                    return parsed_result

        return parsed_result
Beispiel #12
0
class Applicant(models.Model):
    _name = "hr.applicant"
    _description = "Applicant"
    _order = "priority desc, id desc"
    _inherit = ['mail.thread', 'mail.activity.mixin', 'utm.mixin']

    def _default_stage_id(self):
        if self._context.get('default_job_id'):
            ids = self.env['hr.recruitment.stage'].search([
                '|',
                ('job_id', '=', False),
                ('job_id', '=', self._context['default_job_id']),
                ('fold', '=', False)
            ], order='sequence asc', limit=1).ids
            if ids:
                return ids[0]
        return False

    def _default_company_id(self):
        company_id = False
        if self._context.get('default_department_id'):
            department = self.env['hr.department'].browse(self._context['default_department_id'])
            company_id = department.company_id.id
        if not company_id:
            company_id = self.env['res.company']._company_default_get('hr.applicant')
        return company_id

    name = fields.Char("Subject / Application Name", required=True)
    active = fields.Boolean("Active", default=True, help="If the active field is set to false, it will allow you to hide the case without removing it.")
    description = fields.Text("Description")
    email_from = fields.Char("Email", size=128, help="These people will receive email.")
    email_cc = fields.Text("Watchers Emails", size=252,
                           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")
    probability = fields.Float("Probability")
    partner_id = fields.Many2one('res.partner', "Contact")
    create_date = fields.Datetime("Creation Date", readonly=True, index=True)
    stage_id = fields.Many2one('hr.recruitment.stage', 'Stage', ondelete='restrict', track_visibility='onchange',
                               domain="['|', ('job_id', '=', False), ('job_id', '=', job_id)]",
                               copy=False, index=True,
                               group_expand='_read_group_stage_ids',
                               default=_default_stage_id)
    last_stage_id = fields.Many2one('hr.recruitment.stage', "Last Stage",
                                    help="Stage of the applicant before being in the current stage. Used for lost cases analysis.")
    categ_ids = fields.Many2many('hr.applicant.category', string="Tags")
    company_id = fields.Many2one('res.company', "Company", default=_default_company_id)
    user_id = fields.Many2one('res.users', "Responsible", track_visibility="onchange", default=lambda self: self.env.uid)
    date_closed = fields.Datetime("Closed", readonly=True, index=True)
    date_open = fields.Datetime("Assigned", readonly=True, index=True)
    date_last_stage_update = fields.Datetime("Last Stage Update", index=True, default=fields.Datetime.now)
    priority = fields.Selection(AVAILABLE_PRIORITIES, "Appreciation", default='0')
    job_id = fields.Many2one('hr.job', "Applied Job")
    salary_proposed_extra = fields.Char("Proposed Salary Extra", help="Salary Proposed by the Organisation, extra advantages")
    salary_expected_extra = fields.Char("Expected Salary Extra", help="Salary Expected by Applicant, extra advantages")
    salary_proposed = fields.Float("Proposed Salary", group_operator="avg", help="Salary Proposed by the Organisation")
    salary_expected = fields.Float("Expected Salary", group_operator="avg", help="Salary Expected by Applicant")
    availability = fields.Date("Availability", help="The date at which the applicant will be available to start working")
    partner_name = fields.Char("Applicant's Name")
    partner_phone = fields.Char("Phone", size=32)
    partner_mobile = fields.Char("Mobile", size=32)
    type_id = fields.Many2one('hr.recruitment.degree', "Degree")
    department_id = fields.Many2one('hr.department', "Department")
    reference = fields.Char("Referred By")
    day_open = fields.Float(compute='_compute_day', string="Days to Open")
    day_close = fields.Float(compute='_compute_day', string="Days to Close")
    delay_close = fields.Float(compute="_compute_day", string='Delay to Close', readonly=True, group_operator="avg", help="Number of days to close", store=True)
    color = fields.Integer("Color Index", default=0)
    emp_id = fields.Many2one('hr.employee', string="Employee", track_visibility="onchange", help="Employee linked to the applicant.")
    user_email = fields.Char(related='user_id.email', type="char", string="User Email", readonly=True)
    attachment_number = fields.Integer(compute='_get_attachment_number', string="Number of Attachments")
    employee_name = fields.Char(related='emp_id.name', string="Employee Name", readonly=False)
    attachment_ids = fields.One2many('ir.attachment', 'res_id', domain=[('res_model', '=', 'hr.applicant')], string='Attachments')
    kanban_state = fields.Selection([
        ('normal', 'Grey'),
        ('done', 'Green'),
        ('blocked', 'Red')], string='Kanban State',
        copy=False, default='normal', required=True)
    legend_blocked = fields.Char(related='stage_id.legend_blocked', string='Kanban Blocked', readonly=False)
    legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid', readonly=False)
    legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing', readonly=False)
    

    @api.depends('date_open', 'date_closed')
    @api.one
    def _compute_day(self):
        if self.date_open:
            date_create = self.create_date
            date_open = self.date_open
            self.day_open = (date_open - date_create).total_seconds() / (24.0 * 3600)

        if self.date_closed:
            date_create = self.create_date
            date_closed = self.date_closed
            self.day_close = (date_closed - date_create).total_seconds() / (24.0 * 3600)
            self.delay_close = self.day_close - self.day_open

    @api.multi
    def _get_attachment_number(self):
        read_group_res = self.env['ir.attachment'].read_group(
            [('res_model', '=', 'hr.applicant'), ('res_id', 'in', self.ids)],
            ['res_id'], ['res_id'])
        attach_data = dict((res['res_id'], res['res_id_count']) for res in read_group_res)
        for record in self:
            record.attachment_number = attach_data.get(record.id, 0)

    @api.model
    def _read_group_stage_ids(self, stages, domain, order):
        # retrieve job_id from the context and write the domain: ids + contextual columns (job or default)
        job_id = self._context.get('default_job_id')
        search_domain = [('job_id', '=', False)]
        if job_id:
            search_domain = ['|', ('job_id', '=', job_id)] + search_domain
        if stages:
            search_domain = ['|', ('id', 'in', stages.ids)] + search_domain

        stage_ids = stages._search(search_domain, order=order, access_rights_uid=SUPERUSER_ID)
        return stages.browse(stage_ids)

    @api.onchange('job_id')
    def onchange_job_id(self):
        vals = self._onchange_job_id_internal(self.job_id.id)
        self.department_id = vals['value']['department_id']
        self.user_id = vals['value']['user_id']
        self.stage_id = vals['value']['stage_id']

    def _onchange_job_id_internal(self, job_id):
        department_id = False
        user_id = False
        stage_id = self.stage_id.id or self._context.get('default_stage_id')
        if job_id:
            job = self.env['hr.job'].browse(job_id)
            department_id = job.department_id.id
            user_id = job.user_id.id
            if not stage_id:
                stage_ids = self.env['hr.recruitment.stage'].search([
                    '|',
                    ('job_id', '=', False),
                    ('job_id', '=', job.id),
                    ('fold', '=', False)
                ], order='sequence asc', limit=1).ids
                stage_id = stage_ids[0] if stage_ids else False

        return {'value': {
            'department_id': department_id,
            'user_id': user_id,
            'stage_id': stage_id
        }}

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        self.partner_phone = self.partner_id.phone
        self.partner_mobile = self.partner_id.mobile
        self.email_from = self.partner_id.email

    @api.onchange('stage_id')
    def onchange_stage_id(self):
        vals = self._onchange_stage_id_internal(self.stage_id.id)
        if vals['value'].get('date_closed'):
            self.date_closed = vals['value']['date_closed']

    def _onchange_stage_id_internal(self, stage_id):
        if not stage_id:
            return {'value': {}}
        stage = self.env['hr.recruitment.stage'].browse(stage_id)
        if stage.fold:
            return {'value': {'date_closed': fields.datetime.now()}}
        return {'value': {'date_closed': False}}

    @api.model
    def create(self, vals):
        if vals.get('department_id') and not self._context.get('default_department_id'):
            self = self.with_context(default_department_id=vals.get('department_id'))
        if vals.get('job_id') or self._context.get('default_job_id'):
            job_id = vals.get('job_id') or self._context.get('default_job_id')
            for key, value in self._onchange_job_id_internal(job_id)['value'].items():
                if key not in vals:
                    vals[key] = value
        if vals.get('user_id'):
            vals['date_open'] = fields.Datetime.now()
        if 'stage_id' in vals:
            vals.update(self._onchange_stage_id_internal(vals.get('stage_id'))['value'])
        return super(Applicant, self.with_context(mail_create_nolog=True)).create(vals)

    @api.multi
    def write(self, vals):
        # user_id change: update date_open
        if vals.get('user_id'):
            vals['date_open'] = fields.Datetime.now()
        # stage_id: track last stage before update
        if 'stage_id' in vals:
            vals['date_last_stage_update'] = fields.Datetime.now()
            vals.update(self._onchange_stage_id_internal(vals.get('stage_id'))['value'])
            if 'kanban_state' not in vals:
                vals['kanban_state'] = 'normal'
            for applicant in self:
                vals['last_stage_id'] = applicant.stage_id.id
                res = super(Applicant, self).write(vals)
        else:
            res = super(Applicant, self).write(vals)
        return res

    @api.model
    def get_empty_list_help(self, help):
        return super(Applicant, self.with_context(empty_list_help_model='hr.job',
                                                  empty_list_help_id=self.env.context.get('default_job_id'),
                                                  empty_list_help_document_name=_("job applicant"))).get_empty_list_help(help)

    @api.multi
    def action_get_created_employee(self):
        self.ensure_one()
        action = self.env['ir.actions.act_window'].for_xml_id('hr', 'open_view_employee_list')
        action['res_id'] = self.mapped('emp_id').ids[0]
        return action

    @api.multi
    def action_makeMeeting(self):
        """ This opens Meeting's calendar view to schedule meeting on current applicant
            @return: Dictionary value for created Meeting view
        """
        self.ensure_one()
        partners = self.partner_id | self.user_id.partner_id | self.department_id.manager_id.user_id.partner_id

        category = self.env.ref('hr_recruitment.categ_meet_interview')
        res = self.env['ir.actions.act_window'].for_xml_id('calendar', 'action_calendar_event')
        res['context'] = {
            'default_partner_ids': partners.ids,
            'default_user_id': self.env.uid,
            'default_name': self.name,
            'default_categ_ids': category and [category.id] or False,
        }
        return res

    @api.multi
    def action_get_attachment_tree_view(self):
        attachment_action = self.env.ref('base.action_attachment')
        action = attachment_action.read()[0]
        action['context'] = {'default_res_model': self._name, 'default_res_id': self.ids[0]}
        action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', self.ids)])
        action['search_view_id'] = (self.env.ref('hr_recruitment.ir_attachment_view_search_inherit_hr_recruitment').id, )
        return action

    @api.multi
    def _track_template(self, tracking):
        res = super(Applicant, self)._track_template(tracking)
        applicant = self[0]
        changes, dummy = tracking[applicant.id]
        if 'stage_id' in changes and applicant.stage_id.template_id:
            res['stage_id'] = (applicant.stage_id.template_id, {
                'auto_delete_message': True,
                'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
                'notif_layout': 'mail.mail_notification_light'
            })
        return res

    @api.multi
    def _track_subtype(self, init_values):
        record = self[0]
        if 'emp_id' in init_values and record.emp_id and record.emp_id.active:
            return 'hr_recruitment.mt_applicant_hired'
        elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence <= 1:
            return 'hr_recruitment.mt_applicant_new'
        elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence > 1:
            return 'hr_recruitment.mt_applicant_stage_changed'
        return super(Applicant, self)._track_subtype(init_values)

    @api.multi
    def _notify_get_reply_to(self, default=None, records=None, company=None, doc_names=None):
        """ Override to set alias of applicants to their job definition if any. """
        aliases = self.mapped('job_id')._notify_get_reply_to(default=default, records=None, company=company, doc_names=None)
        res = {app.id: aliases.get(app.job_id.id) for app in self}
        leftover = self.filtered(lambda rec: not rec.job_id)
        if leftover:
            res.update(super(Applicant, leftover)._notify_get_reply_to(default=default, records=None, company=company, doc_names=doc_names))
        return res

    @api.multi
    def message_get_suggested_recipients(self):
        recipients = super(Applicant, self).message_get_suggested_recipients()
        for applicant in self:
            if applicant.partner_id:
                applicant._message_add_suggested_recipient(recipients, partner=applicant.partner_id, reason=_('Contact'))
            elif applicant.email_from:
                applicant._message_add_suggested_recipient(recipients, email=applicant.email_from, reason=_('Contact Email'))
        return recipients

    @api.model
    def message_new(self, msg, custom_values=None):
        """ Overrides mail_thread message_new that is called by the mailgateway
            through message_process.
            This override updates the document according to the email.
        """
        # remove default author when going through the mail gateway. Indeed we
        # do not want to explicitly set user_id to False; however we do not
        # want the gateway user to be responsible if no other responsible is
        # found.
        self = self.with_context(default_user_id=False)
        val = msg.get('from').split('<')[0]
        defaults = {
            'name': msg.get('subject') or _("No Subject"),
            'partner_name': val,
            'email_from': msg.get('from'),
            'email_cc': msg.get('cc'),
            'partner_id': msg.get('author_id', False),
        }
        if msg.get('priority'):
            defaults['priority'] = msg.get('priority')
        if custom_values:
            defaults.update(custom_values)
        return super(Applicant, self).message_new(msg, custom_values=defaults)

    def _message_post_after_hook(self, message, *args, **kwargs):
        if self.email_from and not self.partner_id:
            # we consider that posting a message with a specified recipient (not a follower, a specific one)
            # on a document without customer means that it was created through the chatter using
            # suggested recipients. This heuristic allows to avoid ugly hacks in JS.
            new_partner = message.partner_ids.filtered(lambda partner: partner.email == self.email_from)
            if new_partner:
                self.search([
                    ('partner_id', '=', False),
                    ('email_from', '=', new_partner.email),
                    ('stage_id.fold', '=', False)]).write({'partner_id': new_partner.id})
        return super(Applicant, self)._message_post_after_hook(message, *args, **kwargs)

    @api.multi
    def create_employee_from_applicant(self):
        """ Create an hr.employee from the hr.applicants """
        employee = False
        for applicant in self:
            contact_name = False
            if applicant.partner_id:
                address_id = applicant.partner_id.address_get(['contact'])['contact']
                contact_name = applicant.partner_id.name_get()[0][1]
            else :
                new_partner_id = self.env['res.partner'].create({
                    'is_company': False,
                    'name': applicant.partner_name,
                    'email': applicant.email_from,
                    'phone': applicant.partner_phone,
                    'mobile': applicant.partner_mobile
                })
                address_id = new_partner_id.address_get(['contact'])['contact']
            if applicant.job_id and (applicant.partner_name or contact_name):
                applicant.job_id.write({'no_of_hired_employee': applicant.job_id.no_of_hired_employee + 1})
                employee = self.env['hr.employee'].create({
                    'name': applicant.partner_name or contact_name,
                    'job_id': applicant.job_id.id,
                    'address_home_id': address_id,
                    'department_id': applicant.department_id.id or False,
                    'address_id': applicant.company_id and applicant.company_id.partner_id
                            and applicant.company_id.partner_id.id or False,
                    'work_email': applicant.department_id and applicant.department_id.company_id
                            and applicant.department_id.company_id.email or False,
                    'work_phone': applicant.department_id and applicant.department_id.company_id
                            and applicant.department_id.company_id.phone or False})
                applicant.write({'emp_id': employee.id})
                applicant.job_id.message_post(
                    body=_('New Employee %s Hired') % applicant.partner_name if applicant.partner_name else applicant.name,
                    subtype="hr_recruitment.mt_job_applicant_hired")
            else:
                raise UserError(_('You must define an Applied Job and a Contact Name for this applicant.'))

        employee_action = self.env.ref('hr.open_view_employee_list')
        dict_act_window = employee_action.read([])[0]
        dict_act_window['context'] = {'form_view_initial_mode': 'edit'}
        dict_act_window['res_id'] = employee.id
        return dict_act_window

    @api.multi
    def archive_applicant(self):
        self.write({'active': False})

    @api.multi
    def reset_applicant(self):
        """ Reinsert the applicant into the recruitment pipe in the first stage"""
        default_stage_id = self._default_stage_id()
        self.write({'active': True, 'stage_id': default_stage_id})
Beispiel #13
0
class IrFilters(models.Model):
    _name = 'ir.filters'
    _description = 'Filters'
    _order = 'model_id, name, id desc'

    name = fields.Char(string='Filter Name', translate=True, required=True)
    user_id = fields.Many2one(
        'res.users',
        string='User',
        ondelete='cascade',
        help=
        "The user this filter is private to. When left empty the filter is public "
        "and available to all users.")
    domain = fields.Text(default='[]', required=True)
    context = fields.Text(default='{}', required=True)
    sort = fields.Text(default='[]', required=True)
    model_id = fields.Selection(selection='_list_all_models',
                                string='Model',
                                required=True)
    is_default = fields.Boolean(string='Default Filter')
    action_id = fields.Many2one(
        'ir.actions.actions',
        string='Action',
        ondelete='cascade',
        help="The menu action this filter applies to. "
        "When left empty the filter applies to all menus "
        "for this model.")
    active = fields.Boolean(default=True)

    @api.model
    def _list_all_models(self):
        self._cr.execute("SELECT model, name FROM ir_model ORDER BY name")
        return self._cr.fetchall()

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

    @api.multi
    def _get_eval_domain(self):
        self.ensure_one()
        return ast.literal_eval(self.domain)

    @api.model
    def _get_action_domain(self, action_id=None):
        """Return a domain component for matching filters that are visible in the
           same context (menu/view) as the given action."""
        if action_id:
            # filters specific to this menu + global ones
            return [('action_id', 'in', [action_id, False])]
        # only global ones
        return [('action_id', '=', False)]

    @api.model
    def get_filters(self, model, action_id=None):
        """Obtain the list of filters available for the user on the given model.

        :param action_id: optional ID of action to restrict filters to this action
            plus global filters. If missing only global filters are returned.
            The action does not have to correspond to the model, it may only be
            a contextual action.
        :return: list of :meth:`~osv.read`-like dicts containing the
            ``name``, ``is_default``, ``domain``, ``user_id`` (m2o tuple),
            ``action_id`` (m2o tuple) and ``context`` of the matching ``ir.filters``.
        """
        # available filters: private filters (user_id=uid) and public filters (uid=NULL),
        # and filters for the action (action_id=action_id) or global (action_id=NULL)
        action_domain = self._get_action_domain(action_id)
        filters = self.search(action_domain +
                              [('model_id', '=',
                                model), ('user_id', 'in', [self._uid, False])])
        user_context = self.env['res.users'].context_get()
        return filters.with_context(user_context).read(
            ['name', 'is_default', 'domain', 'context', 'user_id', 'sort'])

    @api.model
    def _check_global_default(self, vals, matching_filters):
        """ _check_global_default(dict, list(dict), dict) -> None

        Checks if there is a global default for the model_id requested.

        If there is, and the default is different than the record being written
        (-> we're not updating the current global default), raise an error
        to avoid users unknowingly overwriting existing global defaults (they
        have to explicitly remove the current default before setting a new one)

        This method should only be called if ``vals`` is trying to set
        ``is_default``

        :raises swerp.exceptions.UserError: if there is an existing default and
                                            we're not updating it
        """
        domain = self._get_action_domain(vals.get('action_id'))
        defaults = self.search(domain + [
            ('model_id', '=', vals['model_id']),
            ('user_id', '=', False),
            ('is_default', '=', True),
        ])

        if not defaults:
            return
        if matching_filters and (matching_filters[0]['id'] == defaults.id):
            return

        raise UserError(
            _("There is already a shared filter set as default for %(model)s, delete or change it before setting a new default"
              ) % {'model': vals.get('model_id')})

    @api.model
    @api.returns('self', lambda value: value.id)
    def create_or_replace(self, vals):
        action_id = vals.get('action_id')
        current_filters = self.get_filters(vals['model_id'], action_id)
        matching_filters = [
            f for f in current_filters
            if f['name'].lower() == vals['name'].lower()
            # next line looks for matching user_ids (specific or global), i.e.
            # f.user_id is False and vals.user_id is False or missing,
            # or f.user_id.id == vals.user_id
            if (f['user_id'] and f['user_id'][0]) == vals.get('user_id')
        ]

        if vals.get('is_default'):
            if vals.get('user_id'):
                # Setting new default: any other default that belongs to the user
                # should be turned off
                domain = self._get_action_domain(action_id)
                defaults = self.search(domain + [
                    ('model_id', '=', vals['model_id']),
                    ('user_id', '=', vals['user_id']),
                    ('is_default', '=', True),
                ])
                if defaults:
                    defaults.write({'is_default': False})
            else:
                self._check_global_default(vals, matching_filters)

        # When a filter exists for the same (name, model, user) triple, we simply
        # replace its definition (considering action_id irrelevant here)
        if matching_filters:
            matching_filter = self.browse(matching_filters[0]['id'])
            matching_filter.write(vals)
            return matching_filter

        return self.create(vals)

    _sql_constraints = [
        # Partial constraint, complemented by unique index (see below). Still
        # useful to keep because it provides a proper error message when a
        # violation occurs, as it shares the same prefix as the unique index.
        ('name_model_uid_unique',
         'unique (name, model_id, user_id, action_id)',
         'Filter names must be unique'),
    ]

    @api.model_cr_context
    def _auto_init(self):
        result = super(IrFilters, self)._auto_init()
        # Use unique index to implement unique constraint on the lowercase name (not possible using a constraint)
        tools.create_unique_index(
            self._cr, 'ir_filters_name_model_uid_unique_action_index',
            self._table, [
                'lower(name)', 'model_id', 'COALESCE(user_id,-1)',
                'COALESCE(action_id,-1)'
            ])
        return result
Beispiel #14
0
class RepairLine(models.Model):
    _name = 'repair.line'
    _description = 'Repair Line (parts)'

    name = fields.Text('Description', required=True)
    repair_id = fields.Many2one('repair.order',
                                'Repair Order Reference',
                                index=True,
                                ondelete='cascade')
    type = fields.Selection([('add', 'Add'), ('remove', 'Remove')],
                            'Type',
                            default='add',
                            required=True)
    product_id = fields.Many2one('product.product', 'Product', required=True)
    invoiced = fields.Boolean('Invoiced', copy=False, readonly=True)
    price_unit = fields.Float('Unit Price',
                              required=True,
                              digits=dp.get_precision('Product Price'))
    price_subtotal = fields.Float('Subtotal',
                                  compute='_compute_price_subtotal',
                                  store=True,
                                  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',
        default=1.0,
        digits=dp.get_precision('Product Unit of Measure'),
        required=True)
    product_uom = fields.Many2one('uom.uom',
                                  'Product Unit of Measure',
                                  required=True)
    invoice_line_id = fields.Many2one('account.invoice.line',
                                      'Invoice Line',
                                      copy=False,
                                      readonly=True)
    location_id = fields.Many2one('stock.location',
                                  'Source Location',
                                  index=True,
                                  required=True)
    location_dest_id = fields.Many2one('stock.location',
                                       'Dest. Location',
                                       index=True,
                                       required=True)
    move_id = fields.Many2one('stock.move',
                              'Inventory Move',
                              copy=False,
                              readonly=True)
    lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial')
    state = fields.Selection(
        [('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'),
         ('cancel', 'Cancelled')],
        'Status',
        default='draft',
        copy=False,
        readonly=True,
        required=True,
        help=
        'The status of a repair line is set automatically to the one of the linked repair order.'
    )

    @api.constrains('lot_id', 'product_id')
    def constrain_lot_id(self):
        for line in self.filtered(
                lambda x: x.product_id.tracking != 'none' and not x.lot_id):
            raise ValidationError(
                _("Serial number is required for operation line with product '%s'"
                  ) % (line.product_id.name))

    @api.one
    @api.depends('price_unit', 'repair_id', 'product_uom_qty', 'product_id',
                 'repair_id.invoice_method')
    def _compute_price_subtotal(self):
        taxes = self.tax_id.compute_all(
            self.price_unit, self.repair_id.pricelist_id.currency_id,
            self.product_uom_qty, self.product_id, self.repair_id.partner_id)
        self.price_subtotal = taxes['total_excluded']

    @api.onchange('type', 'repair_id')
    def onchange_operation_type(self):
        """ 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 self.type:
            self.location_id = False
            self.location_dest_id = False
        elif self.type == 'add':
            self.onchange_product_id()
            args = self.repair_id.company_id and [
                ('company_id', '=', self.repair_id.company_id.id)
            ] or []
            warehouse = self.env['stock.warehouse'].search(args, limit=1)
            self.location_id = warehouse.lot_stock_id
            self.location_dest_id = self.env['stock.location'].search(
                [('usage', '=', 'production')], limit=1).id
        else:
            self.price_unit = 0.0
            self.tax_id = False
            self.location_id = self.env['stock.location'].search(
                [('usage', '=', 'production')], limit=1).id
            self.location_dest_id = self.env['stock.location'].search(
                [('scrap_location', '=', True)], limit=1).id

    @api.onchange('repair_id', 'product_id', 'product_uom_qty')
    def onchange_product_id(self):
        """ On change of product it sets product quantity, tax account, name,
        uom of product, unit price and price subtotal. """
        partner = self.repair_id.partner_id
        pricelist = self.repair_id.pricelist_id
        if not self.product_id or not self.product_uom_qty:
            return
        if self.product_id:
            if partner:
                self.name = self.product_id.with_context(
                    lang=partner.lang).display_name
            else:
                self.name = self.product_id.display_name
            if self.product_id.description_sale:
                if partner:
                    self.name += '\n' + self.product_id.with_context(
                        lang=partner.lang).description_sale
                else:
                    self.name += '\n' + self.product_id.description_sale
            self.product_uom = self.product_id.uom_id.id
        if self.type != 'remove':
            if partner and self.product_id:
                fp = partner.property_account_position_id
                if not fp:
                    # Check automatic detection
                    fp_id = self.env[
                        'account.fiscal.position'].get_fiscal_position(
                            partner.id,
                            delivery_id=self.repair_id.address_id.id)
                    fp = self.env['account.fiscal.position'].browse(fp_id)
                self.tax_id = fp.map_tax(self.product_id.taxes_id,
                                         self.product_id, partner).ids
            warning = False
            if not pricelist:
                warning = {
                    'title':
                    _('No pricelist found.'),
                    'message':
                    _('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.'
                      )
                }
                return {'warning': warning}
            else:
                self._onchange_product_uom()

    @api.onchange('product_uom')
    def _onchange_product_uom(self):
        partner = self.repair_id.partner_id
        pricelist = self.repair_id.pricelist_id
        if pricelist and self.product_id and self.type != 'remove':
            price = pricelist.get_product_price(self.product_id,
                                                self.product_uom_qty,
                                                partner,
                                                uom_id=self.product_uom.id)
            if price is False:
                warning = {
                    'title':
                    _('No valid pricelist line found.'),
                    'message':
                    _("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist."
                      )
                }
                return {'warning': warning}
            else:
                self.price_unit = price
Beispiel #15
0
class SetupBarBankConfigWizard(models.TransientModel):
    _inherits = {'res.partner.bank': 'res_partner_bank_id'}
    _name = 'account.setup.bank.manual.config'
    _description = 'Bank setup manual config'

    res_partner_bank_id = fields.Many2one(comodel_name='res.partner.bank',
                                          ondelete='cascade',
                                          required=True)
    create_or_link_option = fields.Selection(selection=[
        ('new', 'Create new journal'), ('link', 'Link to an existing journal')
    ],
                                             default='new')
    new_journal_name = fields.Char(
        compute='compute_new_journal_related_data',
        inverse='set_linked_journal_id',
        required=True,
        help='Will be used to name the Journal related to this bank account')
    linked_journal_id = fields.Many2one(string="Journal",
                                        comodel_name='account.journal',
                                        compute='compute_linked_journal_id',
                                        inverse='set_linked_journal_id')
    new_journal_code = fields.Char(
        string="Code",
        required=True,
        default=lambda self: self.env[
            'account.journal'].get_next_bank_cash_default_code(
                'bank', self.env['res.company']._company_default_get(
                    'account.journal').id))

    # field computing the type of the res.patrner.bank. It's behaves the same as a related res_part_bank_id.acc_type
    # except we want to display  this information while the record isn't yet saved.
    related_acc_type = fields.Selection(
        string="Account Type",
        selection=lambda x: x.env['res.partner.bank'
                                  ].get_supported_account_types(),
        compute='_compute_related_acc_type')

    @api.depends('acc_number')
    def _compute_related_acc_type(self):
        for record in self:
            record.related_acc_type = self.env[
                'res.partner.bank'].retrieve_acc_type(record.acc_number)

    @api.model
    def create(self, vals):
        """ This wizard is only used to setup an account for the current active
        company, so we always inject the corresponding partner when creating
        the model.
        """
        vals['partner_id'] = self.env.user.company_id.partner_id.id
        return super(SetupBarBankConfigWizard, self).create(vals)

    @api.depends('linked_journal_id')
    def compute_new_journal_related_data(self):
        for record in self:
            if record.linked_journal_id:
                record.new_journal_name = record.linked_journal_id.name

    @api.depends('journal_id'
                 )  # Despite its name, journal_id is actually a One2many field
    def compute_linked_journal_id(self):
        for record in self:
            record.linked_journal_id = record.journal_id and record.journal_id[
                0] or None

    def set_linked_journal_id(self):
        """ Called when saving the wizard.
        """
        for record in self:
            selected_journal = record.linked_journal_id
            if record.create_or_link_option == 'new':
                company = self.env['res.company']._company_default_get(
                    'account.journal')
                selected_journal = self.env['account.journal'].create({
                    'name':
                    record.new_journal_name,
                    'code':
                    record.new_journal_code,
                    'type':
                    'bank',
                    'company_id':
                    company.id,
                    'bank_account_id':
                    record.res_partner_bank_id.id,
                })
            else:
                selected_journal.bank_account_id = record.res_partner_bank_id.id

    def validate(self):
        """ Called by the validation button of this wizard. Serves as an
        extension hook in account_bank_statement_import.
        """
        self.env.user.company_id.set_onboarding_step_done(
            'account_setup_bank_data_state')
Beispiel #16
0
class FetchmailServer(models.Model):
    """Incoming POP/IMAP mail server account"""

    _name = 'fetchmail.server'
    _description = 'Incoming Mail Server'
    _order = 'priority'

    name = fields.Char('Name', required=True)
    active = fields.Boolean('Active', default=True)
    state = fields.Selection([
        ('draft', 'Not Confirmed'),
        ('done', 'Confirmed'),
    ],
                             string='Status',
                             index=True,
                             readonly=True,
                             copy=False,
                             default='draft')
    server = fields.Char(string='Server Name',
                         readonly=True,
                         help="Hostname or IP of the mail server",
                         states={'draft': [('readonly', False)]})
    port = fields.Integer(readonly=True,
                          states={'draft': [('readonly', False)]})
    type = fields.Selection([
        ('pop', 'POP Server'),
        ('imap', 'IMAP Server'),
        ('local', 'Local Server'),
    ],
                            'Server Type',
                            index=True,
                            required=True,
                            default='pop')
    is_ssl = fields.Boolean(
        'SSL/TLS',
        help=
        "Connections are encrypted with SSL/TLS through a dedicated port (default: IMAPS=993, POP3S=995)"
    )
    attach = fields.Boolean(
        'Keep Attachments',
        help="Whether attachments should be downloaded. "
        "If not enabled, incoming emails will be stripped of any attachments before being processed",
        default=True)
    original = fields.Boolean(
        'Keep Original',
        help=
        "Whether a full original copy of each email should be kept for reference "
        "and attached to each processed message. This will usually double the size of your message database."
    )
    date = fields.Datetime(string='Last Fetch Date', readonly=True)
    user = fields.Char(string='Username',
                       readonly=True,
                       states={'draft': [('readonly', False)]})
    password = fields.Char(readonly=True,
                           states={'draft': [('readonly', False)]})
    object_id = fields.Many2one(
        'ir.model',
        string="Create a New Record",
        help="Process each incoming mail as part of a conversation "
        "corresponding to this document type. This will create "
        "new documents for new conversations, or attach follow-up "
        "emails to the existing conversations (documents).")
    priority = fields.Integer(
        string='Server Priority',
        readonly=True,
        states={'draft': [('readonly', False)]},
        help=
        "Defines the order of processing, lower values mean higher priority",
        default=5)
    message_ids = fields.One2many('mail.mail',
                                  'fetchmail_server_id',
                                  string='Messages',
                                  readonly=True)
    configuration = fields.Text('Configuration', readonly=True)
    script = fields.Char(readonly=True,
                         default='/mail/static/scripts/openerp_mailgate.py')

    @api.onchange('type', 'is_ssl', 'object_id')
    def onchange_server_type(self):
        self.port = 0
        if self.type == 'pop':
            self.port = self.is_ssl and 995 or 110
        elif self.type == 'imap':
            self.port = self.is_ssl and 993 or 143
        else:
            self.server = ''

        conf = {
            'dbname': self.env.cr.dbname,
            'uid': self.env.uid,
            'model': self.object_id.model if self.object_id else 'MODELNAME'
        }
        self.configuration = """
            Use the below script with the following command line options with your Mail Transport Agent (MTA)
            openerp_mailgate.py --host=HOSTNAME --port=PORT -u %(uid)d -p PASSWORD -d %(dbname)s
            Example configuration for the postfix mta running locally:
            /etc/postfix/virtual_aliases:
            @youdomain openerp_mailgate@localhost
            /etc/aliases:
            openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p PASSWORD -d %(dbname)s"
        """ % conf

    @api.model
    def create(self, values):
        res = super(FetchmailServer, self).create(values)
        self._update_cron()
        return res

    @api.multi
    def write(self, values):
        res = super(FetchmailServer, self).write(values)
        self._update_cron()
        return res

    @api.multi
    def unlink(self):
        res = super(FetchmailServer, self).unlink()
        self._update_cron()
        return res

    @api.multi
    def set_draft(self):
        self.write({'state': 'draft'})
        return True

    @api.multi
    def connect(self):
        self.ensure_one()
        if self.type == 'imap':
            if self.is_ssl:
                connection = IMAP4_SSL(self.server, int(self.port))
            else:
                connection = IMAP4(self.server, int(self.port))
            connection.login(self.user, self.password)
        elif self.type == 'pop':
            if self.is_ssl:
                connection = POP3_SSL(self.server, int(self.port))
            else:
                connection = POP3(self.server, int(self.port))
            #TODO: use this to remove only unread messages
            #connection.user("recent:"+server.user)
            connection.user(self.user)
            connection.pass_(self.password)
        # Add timeout on socket
        connection.sock.settimeout(MAIL_TIMEOUT)
        return connection

    @api.multi
    def button_confirm_login(self):
        for server in self:
            try:
                connection = server.connect()
                server.write({'state': 'done'})
            except Exception as err:
                _logger.info("Failed to connect to %s server %s.",
                             server.type,
                             server.name,
                             exc_info=True)
                raise UserError(
                    _("Connection test failed: %s") % tools.ustr(err))
            finally:
                try:
                    if connection:
                        if server.type == 'imap':
                            connection.close()
                        elif server.type == 'pop':
                            connection.quit()
                except Exception:
                    # ignored, just a consequence of the previous exception
                    pass
        return True

    @api.model
    def _fetch_mails(self):
        """ Method called by cron to fetch mails from servers """
        return self.search([('state', '=', 'done'),
                            ('type', 'in', ['pop', 'imap'])]).fetch_mail()

    @api.multi
    def fetch_mail(self):
        """ WARNING: meant for cron usage only - will commit() after each email! """
        additionnal_context = {'fetchmail_cron_running': True}
        MailThread = self.env['mail.thread']
        for server in self:
            _logger.info('start checking for new emails on %s server %s',
                         server.type, server.name)
            additionnal_context['fetchmail_server_id'] = server.id
            additionnal_context['server_type'] = server.type
            count, failed = 0, 0
            imap_server = None
            pop_server = None
            if server.type == 'imap':
                try:
                    imap_server = server.connect()
                    imap_server.select()
                    result, data = imap_server.search(None, '(UNSEEN)')
                    for num in data[0].split():
                        res_id = None
                        result, data = imap_server.fetch(num, '(RFC822)')
                        imap_server.store(num, '-FLAGS', '\\Seen')
                        try:
                            res_id = MailThread.with_context(
                                **additionnal_context).message_process(
                                    server.object_id.model,
                                    data[0][1],
                                    save_original=server.original,
                                    strip_attachments=(not server.attach))
                        except Exception:
                            _logger.info(
                                'Failed to process mail from %s server %s.',
                                server.type,
                                server.name,
                                exc_info=True)
                            failed += 1
                        imap_server.store(num, '+FLAGS', '\\Seen')
                        self._cr.commit()
                        count += 1
                    _logger.info(
                        "Fetched %d email(s) on %s server %s; %d succeeded, %d failed.",
                        count, server.type, server.name, (count - failed),
                        failed)
                except Exception:
                    _logger.info(
                        "General failure when trying to fetch mail from %s server %s.",
                        server.type,
                        server.name,
                        exc_info=True)
                finally:
                    if imap_server:
                        imap_server.close()
                        imap_server.logout()
            elif server.type == 'pop':
                try:
                    while True:
                        pop_server = server.connect()
                        (num_messages, total_size) = pop_server.stat()
                        pop_server.list()
                        for num in range(
                                1,
                                min(MAX_POP_MESSAGES, num_messages) + 1):
                            (header, messages, octets) = pop_server.retr(num)
                            message = (b'\n').join(messages)
                            res_id = None
                            try:
                                res_id = MailThread.with_context(
                                    **additionnal_context).message_process(
                                        server.object_id.model,
                                        message,
                                        save_original=server.original,
                                        strip_attachments=(not server.attach))
                                pop_server.dele(num)
                            except Exception:
                                _logger.info(
                                    'Failed to process mail from %s server %s.',
                                    server.type,
                                    server.name,
                                    exc_info=True)
                                failed += 1
                            self.env.cr.commit()
                        if num_messages < MAX_POP_MESSAGES:
                            break
                        pop_server.quit()
                        _logger.info(
                            "Fetched %d email(s) on %s server %s; %d succeeded, %d failed.",
                            num_messages, server.type, server.name,
                            (num_messages - failed), failed)
                except Exception:
                    _logger.info(
                        "General failure when trying to fetch mail from %s server %s.",
                        server.type,
                        server.name,
                        exc_info=True)
                finally:
                    if pop_server:
                        pop_server.quit()
            server.write({'date': fields.Datetime.now()})
        return True

    @api.model
    def _update_cron(self):
        if self.env.context.get('fetchmail_cron_running'):
            return
        try:
            # Enabled/Disable cron based on the number of 'done' server of type pop or imap
            cron = self.env.ref('fetchmail.ir_cron_mail_gateway_action')
            cron.toggle(model=self._name,
                        domain=[('state', '=', 'done'),
                                ('type', 'in', ['pop', 'imap'])])
        except ValueError:
            pass
Beispiel #17
0
class SaleAdvancePaymentInv(models.TransientModel):
    _name = "sale.advance.payment.inv"
    _description = "Sales Advance Payment Invoice"

    @api.model
    def _count(self):
        return len(self._context.get('active_ids', []))

    @api.model
    def _get_advance_payment_method(self):
        if self._count() == 1:
            sale_obj = self.env['sale.order']
            order = sale_obj.browse(self._context.get('active_ids'))[0]
            if order.order_line.filtered(lambda dp: dp.is_downpayment) and order.invoice_ids.filtered(lambda invoice: invoice.state != 'cancel') or order.order_line.filtered(lambda l: l.qty_to_invoice < 0):
                return 'all'
            else:
                return 'delivered'
        return 'all'

    @api.model
    def _default_product_id(self):
        product_id = self.env['ir.config_parameter'].sudo().get_param('sale.default_deposit_product_id')
        return self.env['product.product'].browse(int(product_id)).exists()

    @api.model
    def _default_deposit_account_id(self):
        return self._default_product_id().property_account_income_id

    @api.model
    def _default_deposit_taxes_id(self):
        return self._default_product_id().taxes_id

    advance_payment_method = fields.Selection([
        ('delivered', 'Invoiceable lines'),
        ('all', 'Invoiceable lines (deduct down payments)'),
        ('percentage', 'Down payment (percentage)'),
        ('fixed', 'Down payment (fixed amount)')
        ], string='What do you want to invoice?', default=_get_advance_payment_method, required=True)
    product_id = fields.Many2one('product.product', string='Down Payment Product', domain=[('type', '=', 'service')],
        default=_default_product_id)
    count = fields.Integer(default=_count, string='Order Count')
    amount = fields.Float('Down Payment Amount', digits=dp.get_precision('Account'), help="The amount to be invoiced in advance, taxes excluded.")
    deposit_account_id = fields.Many2one("account.account", string="Income Account", domain=[('deprecated', '=', False)],
        help="Account used for deposits", default=_default_deposit_account_id)
    deposit_taxes_id = fields.Many2many("account.tax", string="Customer Taxes", help="Taxes used for deposits", default=_default_deposit_taxes_id)

    @api.onchange('advance_payment_method')
    def onchange_advance_payment_method(self):
        if self.advance_payment_method == 'percentage':
            return {'value': {'amount': 0}}
        return {}

    @api.multi
    def _create_invoice(self, order, so_line, amount):
        inv_obj = self.env['account.invoice']
        ir_property_obj = self.env['ir.property']

        account_id = False
        if self.product_id.id:
            account_id = order.fiscal_position_id.map_account(self.product_id.property_account_income_id or self.product_id.categ_id.property_account_income_categ_id).id
        if not account_id:
            inc_acc = ir_property_obj.get('property_account_income_categ_id', 'product.category')
            account_id = order.fiscal_position_id.map_account(inc_acc).id if inc_acc else False
        if not account_id:
            raise UserError(
                _('There is no income account defined for this product: "%s". You may have to install a chart of account from Accounting app, settings menu.') %
                (self.product_id.name,))

        if self.amount <= 0.00:
            raise UserError(_('The value of the down payment amount must be positive.'))
        context = {'lang': order.partner_id.lang}
        if self.advance_payment_method == 'percentage':
            amount = order.amount_untaxed * self.amount / 100
            name = _("Down payment of %s%%") % (self.amount,)
        else:
            amount = self.amount
            name = _('Down Payment')
        del context
        taxes = self.product_id.taxes_id.filtered(lambda r: not order.company_id or r.company_id == order.company_id)
        if order.fiscal_position_id and taxes:
            tax_ids = order.fiscal_position_id.map_tax(taxes, self.product_id, order.partner_shipping_id).ids
        else:
            tax_ids = taxes.ids

        invoice = inv_obj.create({
            'name': order.client_order_ref or order.name,
            'origin': order.name,
            'type': 'out_invoice',
            'reference': False,
            'account_id': order.partner_id.property_account_receivable_id.id,
            'partner_id': order.partner_invoice_id.id,
            'partner_shipping_id': order.partner_shipping_id.id,
            'invoice_line_ids': [(0, 0, {
                'name': name,
                'origin': order.name,
                'account_id': account_id,
                'price_unit': amount,
                'quantity': 1.0,
                'discount': 0.0,
                'uom_id': self.product_id.uom_id.id,
                'product_id': self.product_id.id,
                'sale_line_ids': [(6, 0, [so_line.id])],
                'invoice_line_tax_ids': [(6, 0, tax_ids)],
                'analytic_tag_ids': [(6, 0, so_line.analytic_tag_ids.ids)],
                'account_analytic_id': order.analytic_account_id.id or False,
            })],
            'currency_id': order.pricelist_id.currency_id.id,
            'payment_term_id': order.payment_term_id.id,
            'fiscal_position_id': order.fiscal_position_id.id or order.partner_id.property_account_position_id.id,
            'team_id': order.team_id.id,
            'user_id': order.user_id.id,
            'company_id': order.company_id.id,
            'comment': order.note,
        })
        invoice.compute_taxes()
        invoice.message_post_with_view('mail.message_origin_link',
                    values={'self': invoice, 'origin': order},
                    subtype_id=self.env.ref('mail.mt_note').id)
        return invoice

    @api.multi
    def create_invoices(self):
        sale_orders = self.env['sale.order'].browse(self._context.get('active_ids', []))

        if self.advance_payment_method == 'delivered':
            sale_orders.action_invoice_create()
        elif self.advance_payment_method == 'all':
            sale_orders.action_invoice_create(final=True)
        else:
            # Create deposit product if necessary
            if not self.product_id:
                vals = self._prepare_deposit_product()
                self.product_id = self.env['product.product'].create(vals)
                self.env['ir.config_parameter'].sudo().set_param('sale.default_deposit_product_id', self.product_id.id)

            sale_line_obj = self.env['sale.order.line']
            for order in sale_orders:
                if self.advance_payment_method == 'percentage':
                    amount = order.amount_untaxed * self.amount / 100
                else:
                    amount = self.amount
                if self.product_id.invoice_policy != 'order':
                    raise UserError(_('The product used to invoice a down payment should have an invoice policy set to "Ordered quantities". Please update your deposit product to be able to create a deposit invoice.'))
                if self.product_id.type != 'service':
                    raise UserError(_("The product used to invoice a down payment should be of type 'Service'. Please use another product or update this product."))
                taxes = self.product_id.taxes_id.filtered(lambda r: not order.company_id or r.company_id == order.company_id)
                if order.fiscal_position_id and taxes:
                    tax_ids = order.fiscal_position_id.map_tax(taxes, self.product_id, order.partner_shipping_id).ids
                else:
                    tax_ids = taxes.ids
                context = {'lang': order.partner_id.lang}
                analytic_tag_ids = []
                for line in order.order_line:
                    analytic_tag_ids = [(4, analytic_tag.id, None) for analytic_tag in line.analytic_tag_ids]
                so_line = sale_line_obj.create({
                    'name': _('Advance: %s') % (time.strftime('%m %Y'),),
                    'price_unit': amount,
                    'product_uom_qty': 0.0,
                    'order_id': order.id,
                    'discount': 0.0,
                    'product_uom': self.product_id.uom_id.id,
                    'product_id': self.product_id.id,
                    'analytic_tag_ids': analytic_tag_ids,
                    'tax_id': [(6, 0, tax_ids)],
                    'is_downpayment': True,
                })
                del context
                self._create_invoice(order, so_line, amount)
        if self._context.get('open_invoices', False):
            return sale_orders.action_view_invoice()
        return {'type': 'ir.actions.act_window_close'}

    def _prepare_deposit_product(self):
        return {
            'name': 'Down payment',
            'type': 'service',
            'invoice_policy': 'order',
            'property_account_income_id': self.deposit_account_id.id,
            'taxes_id': [(6, 0, self.deposit_taxes_id.ids)],
            'company_id': False,
        }
Beispiel #18
0
class BaseAutomation(models.Model):
    _name = 'base.automation'
    _description = 'Automated Action'
    _order = 'sequence'

    action_server_id = fields.Many2one('ir.actions.server',
                                       'Server Actions',
                                       domain="[('model_id', '=', model_id)]",
                                       delegate=True,
                                       required=True,
                                       ondelete='restrict')
    active = fields.Boolean(
        default=True,
        help="When unchecked, the rule is hidden and will not be executed.")
    trigger = fields.Selection([('on_create', 'On Creation'),
                                ('on_write', 'On Update'),
                                ('on_create_or_write', 'On Creation & Update'),
                                ('on_unlink', 'On Deletion'),
                                ('on_change', 'Based on Form Modification'),
                                ('on_time', 'Based on Timed Condition')],
                               string='Trigger Condition',
                               required=True,
                               oldname="kind")
    trg_date_id = fields.Many2one(
        'ir.model.fields',
        string='Trigger Date',
        help="""When should the condition be triggered.
                                  If present, will be checked by the scheduler. If empty, will be checked at creation and update.""",
        domain=
        "[('model_id', '=', model_id), ('ttype', 'in', ('date', 'datetime'))]")
    trg_date_range = fields.Integer(string='Delay after trigger date',
                                    help="""Delay after the trigger date.
                                    You can put a negative number if you need a delay before the
                                    trigger date, like sending a reminder 15 minutes before a meeting."""
                                    )
    trg_date_range_type = fields.Selection([('minutes', 'Minutes'),
                                            ('hour', 'Hours'), ('day', 'Days'),
                                            ('month', 'Months')],
                                           string='Delay type',
                                           default='day')
    trg_date_calendar_id = fields.Many2one(
        "resource.calendar",
        string='Use Calendar',
        help=
        "When calculating a day-based timed condition, it is possible to use a calendar to compute the date based on working days."
    )
    filter_pre_domain = fields.Char(
        string='Before Update Domain',
        help=
        "If present, this condition must be satisfied before the update of the record."
    )
    filter_domain = fields.Char(
        string='Apply on',
        help=
        "If present, this condition must be satisfied before executing the action rule."
    )
    last_run = fields.Datetime(readonly=True, copy=False)
    on_change_fields = fields.Char(
        string="On Change Fields Trigger",
        help="Comma-separated list of field names that triggers the onchange.")

    # which fields have an impact on the registry
    CRITICAL_FIELDS = ['model_id', 'active', 'trigger', 'on_change_fields']

    @api.onchange('model_id')
    def onchange_model_id(self):
        self.model_name = self.model_id.model

    @api.onchange('trigger')
    def onchange_trigger(self):
        if self.trigger in ['on_create', 'on_create_or_write', 'on_unlink']:
            self.filter_pre_domain = self.trg_date_id = self.trg_date_range = self.trg_date_range_type = False
        elif self.trigger in ['on_write', 'on_create_or_write']:
            self.trg_date_id = self.trg_date_range = self.trg_date_range_type = False
        elif self.trigger == 'on_time':
            self.filter_pre_domain = False

    @api.onchange('trigger', 'state')
    def _onchange_state(self):
        if self.trigger == 'on_change' and self.state != 'code':
            ff = self.fields_get(['trigger', 'state'])
            return {
                'warning': {
                    'title': _("Warning"),
                    'message':
                    _("The \"%(trigger_value)s\" %(trigger_label)s can only be used with the \"%(state_value)s\" action type"
                      ) % {
                          'trigger_value':
                          dict(ff['trigger']['selection'])['on_change'],
                          'trigger_label':
                          ff['trigger']['string'],
                          'state_value':
                          dict(ff['state']['selection'])['code'],
                      }
                }
            }

        MAIL_STATES = ('email', 'followers', 'next_activity')
        if self.trigger == 'on_unlink' and self.state in MAIL_STATES:
            return {
                'warning': {
                    'title':
                    _("Warning"),
                    'message':
                    _("You cannot send an email, add followers or create an activity "
                      "for a deleted record.  It simply does not work."),
                }
            }

    @api.model
    def create(self, vals):
        vals['usage'] = 'base_automation'
        base_automation = super(BaseAutomation, self).create(vals)
        self._update_cron()
        self._update_registry()
        return base_automation

    @api.multi
    def write(self, vals):
        res = super(BaseAutomation, self).write(vals)
        if set(vals).intersection(self.CRITICAL_FIELDS):
            self._update_cron()
            self._update_registry()
        return res

    @api.multi
    def unlink(self):
        res = super(BaseAutomation, self).unlink()
        self._update_cron()
        self._update_registry()
        return res

    def _update_cron(self):
        """ Activate the cron job depending on whether there exists action rules
            based on time conditions.
        """
        cron = self.env.ref(
            'base_automation.ir_cron_data_base_automation_check',
            raise_if_not_found=False)
        return cron and cron.toggle(model=self._name,
                                    domain=[('trigger', '=', 'on_time')])

    def _update_registry(self):
        """ Update the registry after a modification on action rules. """
        if self.env.registry.ready and not self.env.context.get('import_file'):
            # for the sake of simplicity, simply force the registry to reload
            self._cr.commit()
            self.env.reset()
            registry = Registry.new(self._cr.dbname)
            registry.registry_invalidated = True

    def _get_actions(self, records, triggers):
        """ Return the actions of the given triggers for records' model. The
            returned actions' context contain an object to manage processing.
        """
        if '__action_done' not in self._context:
            self = self.with_context(__action_done={})
        domain = [('model_name', '=', records._name),
                  ('trigger', 'in', triggers)]
        actions = self.with_context(active_test=True).search(domain)
        return actions.with_env(self.env)

    def _get_eval_context(self):
        """ Prepare the context used when evaluating python code
            :returns: dict -- evaluation context given to safe_eval
        """
        return {
            'datetime': datetime,
            'dateutil': dateutil,
            'time': time,
            'uid': self.env.uid,
            'user': self.env.user,
        }

    def _filter_pre(self, records):
        """ Filter the records that satisfy the precondition of action ``self``. """
        if self.filter_pre_domain and records:
            domain = [('id', 'in', records.ids)] + safe_eval(
                self.filter_pre_domain, self._get_eval_context())
            return records.search(domain)
        else:
            return records

    def _filter_post(self, records):
        return self._filter_post_export_domain(records)[0]

    def _filter_post_export_domain(self, records):
        """ Filter the records that satisfy the postcondition of action ``self``. """
        if self.filter_domain and records:
            domain = [('id', 'in', records.ids)] + safe_eval(
                self.filter_domain, self._get_eval_context())
            return records.search(domain), domain
        else:
            return records, None

    def _process(self, records, domain_post=None):
        """ Process action ``self`` on the ``records`` that have not been done yet. """
        # filter out the records on which self has already been done
        action_done = self._context['__action_done']
        records_done = action_done.get(self, records.browse())
        records -= records_done
        if not records:
            return

        # mark the remaining records as done (to avoid recursive processing)
        action_done = dict(action_done)
        action_done[self] = records_done + records
        self = self.with_context(__action_done=action_done)
        records = records.with_context(__action_done=action_done)

        # modify records
        values = {}
        if 'date_action_last' in records._fields:
            values['date_action_last'] = fields.Datetime.now()
        if values:
            records.write(values)

        # execute server actions
        if self.action_server_id:
            for record in records:
                ctx = {
                    'active_model': record._name,
                    'active_ids': record.ids,
                    'active_id': record.id,
                    'domain_post': domain_post,
                }
                self.action_server_id.with_context(**ctx).run()

    @api.model_cr
    def _register_hook(self):
        """ Patch models that should trigger action rules based on creation,
            modification, deletion of records and form onchanges.
        """

        #
        # Note: the patched methods must be defined inside another function,
        # otherwise their closure may be wrong. For instance, the function
        # create refers to the outer variable 'create', which you expect to be
        # bound to create itself. But that expectation is wrong if create is
        # defined inside a loop; in that case, the variable 'create' is bound to
        # the last function defined by the loop.
        #

        def make_create():
            """ Instanciate a create method that processes action rules. """
            @api.model_create_multi
            def create(self, vals_list, **kw):
                # retrieve the action rules to possibly execute
                actions = self.env['base.automation']._get_actions(
                    self, ['on_create', 'on_create_or_write'])
                # call original method
                records = create.origin(self.with_env(actions.env), vals_list,
                                        **kw)
                # check postconditions, and execute actions on the records that satisfy them
                for action in actions.with_context(old_values=None):
                    action._process(action._filter_post(records))
                return records.with_env(self.env)

            return create

        def make_write():
            """ Instanciate a _write method that processes action rules. """
            #
            # Note: we patch method _write() instead of write() in order to
            # catch updates made by field recomputations.
            #
            @api.multi
            def _write(self, vals, **kw):
                # retrieve the action rules to possibly execute
                actions = self.env['base.automation']._get_actions(
                    self, ['on_write', 'on_create_or_write'])
                records = self.with_env(actions.env)
                # check preconditions on records
                pre = {
                    action: action._filter_pre(records)
                    for action in actions
                }
                # read old values before the update
                old_values = {
                    old_vals.pop('id'): old_vals
                    for old_vals in (records.read(list(vals)) if vals else [])
                }
                # call original method
                _write.origin(records, vals, **kw)
                # check postconditions, and execute actions on the records that satisfy them
                for action in actions.with_context(old_values=old_values):
                    records, domain_post = action._filter_post_export_domain(
                        pre[action])
                    action._process(records, domain_post=domain_post)
                return True

            return _write

        def make_unlink():
            """ Instanciate an unlink method that processes action rules. """
            @api.multi
            def unlink(self, **kwargs):
                # retrieve the action rules to possibly execute
                actions = self.env['base.automation']._get_actions(
                    self, ['on_unlink'])
                records = self.with_env(actions.env)
                # check conditions, and execute actions on the records that satisfy them
                for action in actions:
                    action._process(action._filter_post(records))
                # call original method
                return unlink.origin(self, **kwargs)

            return unlink

        def make_onchange(action_rule_id):
            """ Instanciate an onchange method for the given action rule. """
            def base_automation_onchange(self):
                action_rule = self.env['base.automation'].browse(
                    action_rule_id)
                result = {}
                server_action = action_rule.action_server_id.with_context(
                    active_model=self._name,
                    active_id=None,
                    active_ids=[],
                    onchange_self=self,
                )
                res = server_action.run()
                if res:
                    if 'value' in res:
                        res['value'].pop('id', None)
                        self.update({
                            key: val
                            for key, val in res['value'].items()
                            if key in self._fields
                        })
                    if 'domain' in res:
                        result.setdefault('domain', {}).update(res['domain'])
                    if 'warning' in res:
                        result['warning'] = res['warning']
                return result

            return base_automation_onchange

        patched_models = defaultdict(set)

        def patch(model, name, method):
            """ Patch method `name` on `model`, unless it has been patched already. """
            if model not in patched_models[name]:
                patched_models[name].add(model)
                model._patch_method(name, method)

        # retrieve all actions, and patch their corresponding model
        for action_rule in self.with_context({}).search([]):
            Model = self.env.get(action_rule.model_name)

            # Do not crash if the model of the base_action_rule was uninstalled
            if Model is None:
                _logger.warning("Action rule with ID %d depends on model %s" %
                                (action_rule.id, action_rule.model_name))
                continue

            if action_rule.trigger == 'on_create':
                patch(Model, 'create', make_create())

            elif action_rule.trigger == 'on_create_or_write':
                patch(Model, 'create', make_create())
                patch(Model, '_write', make_write())

            elif action_rule.trigger == 'on_write':
                patch(Model, '_write', make_write())

            elif action_rule.trigger == 'on_unlink':
                patch(Model, 'unlink', make_unlink())

            elif action_rule.trigger == 'on_change':
                # register an onchange method for the action_rule
                method = make_onchange(action_rule.id)
                for field_name in action_rule.on_change_fields.split(","):
                    Model._onchange_methods[field_name.strip()].append(method)

    def _unregister_hook(self):
        """ Remove the patches installed by _register_hook() """
        NAMES = ['create', '_write', 'unlink', '_onchange_methods']
        for Model in self.env.registry.values():
            for name in NAMES:
                try:
                    delattr(Model, name)
                except AttributeError:
                    pass

    @api.model
    def _check_delay(self, action, record, record_dt):
        if action.trg_date_calendar_id and action.trg_date_range_type == 'day':
            return action.trg_date_calendar_id.plan_days(
                action.trg_date_range,
                fields.Datetime.from_string(record_dt),
                compute_leaves=True,
            )
        else:
            delay = DATE_RANGE_FUNCTION[action.trg_date_range_type](
                action.trg_date_range)
            return fields.Datetime.from_string(record_dt) + delay

    @api.model
    def _check(self, automatic=False, use_new_cursor=False):
        """ This Function is called by scheduler. """
        if '__action_done' not in self._context:
            self = self.with_context(__action_done={})

        # retrieve all the action rules to run based on a timed condition
        eval_context = self._get_eval_context()
        for action in self.with_context(active_test=True).search([
            ('trigger', '=', 'on_time')
        ]):
            last_run = fields.Datetime.from_string(
                action.last_run) or datetime.datetime.utcfromtimestamp(0)

            # retrieve all the records that satisfy the action's condition
            domain = []
            context = dict(self._context)
            if action.filter_domain:
                domain = safe_eval(action.filter_domain, eval_context)
            records = self.env[action.model_name].with_context(context).search(
                domain)

            # determine when action should occur for the records
            if action.trg_date_id.name == 'date_action_last' and 'create_date' in records._fields:
                get_record_dt = lambda record: record[action.trg_date_id.name
                                                      ] or record.create_date
            else:
                get_record_dt = lambda record: record[action.trg_date_id.name]

            # process action on the records that should be executed
            now = datetime.datetime.now()
            for record in records:
                record_dt = get_record_dt(record)
                if not record_dt:
                    continue
                action_dt = self._check_delay(action, record, record_dt)
                if last_run <= action_dt < now:
                    try:
                        action._process(record)
                    except Exception:
                        _logger.error(traceback.format_exc())

            action.write(
                {'last_run': now.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})

            if automatic:
                # auto-commit for batch processing
                self._cr.commit()
Beispiel #19
0
class Badge(models.Model):
    _inherit = 'gamification.badge'

    level = fields.Selection([('bronze', 'bronze'), ('silver', 'silver'),
                              ('gold', 'gold')],
                             string='Forum Badge Level')
Beispiel #20
0
class Team(models.Model):
    _name = 'crm.team'
    _inherit = ['mail.alias.mixin', 'crm.team']
    _description = 'Sales Channels'

    use_leads = fields.Boolean(
        'Leads',
        help=
        "Check this box to filter and qualify incoming requests as leads before converting them into opportunities and assigning them to a salesperson."
    )
    use_opportunities = fields.Boolean(
        'Pipeline',
        help="Check this box to manage a presales process with opportunities.")
    alias_id = fields.Many2one(
        'mail.alias',
        string='Alias',
        ondelete="restrict",
        required=True,
        help=
        "The email address associated with this channel. New emails received will automatically create new leads assigned to the channel."
    )
    unassigned_leads_count = fields.Integer(
        compute='_compute_unassigned_leads_count',
        string='Unassigned Leads',
        readonly=True)
    opportunities_count = fields.Integer(compute='_compute_opportunities',
                                         string='Number of open opportunities',
                                         readonly=True)
    opportunities_amount = fields.Integer(compute='_compute_opportunities',
                                          string='Opportunities Revenues',
                                          readonly=True)
    dashboard_graph_model = fields.Selection(selection_add=[('crm.lead',
                                                             'Pipeline')])
    dashboard_graph_period_pipeline = fields.Selection(
        [
            ('week', 'Within a Week'),
            ('month', 'Within a Month'),
            ('year', 'Within a Year'),
        ],
        string='Expected to Close',
        help="The time period this channel's dashboard graph will consider.",
        compute="_compute_dashboard_graph_period_pipeline",
        inverse="_inverse_dashboard_graph_period_pipeline")
    dashboard_graph_group_pipeline = fields.Selection(
        [
            ('day', 'Expected Closing Day'),
            ('week', 'Expected Closing Week'),
            ('month', 'Expected Closing Month'),
            ('user', 'Salesperson'),
            ('stage', 'Stage'),
        ],
        string='Grouping Method',
        default='day',
        help="How this channel's dashboard graph will group the results.")

    def _compute_unassigned_leads_count(self):
        leads_data = self.env['crm.lead'].read_group([
            ('team_id', 'in', self.ids),
            ('type', '=', 'lead'),
            ('user_id', '=', False),
        ], ['team_id'], ['team_id'])
        counts = {
            datum['team_id'][0]: datum['team_id_count']
            for datum in leads_data
        }
        for team in self:
            team.unassigned_leads_count = counts.get(team.id, 0)

    def _compute_opportunities(self):
        opportunity_data = self.env['crm.lead'].search([
            ('team_id', 'in', self.ids),
            ('probability', '<', 100),
            ('type', '=', 'opportunity'),
        ]).read(['planned_revenue', 'probability', 'team_id'])
        counts = {}
        amounts = {}
        for datum in opportunity_data:
            counts.setdefault(datum['team_id'][0], 0)
            amounts.setdefault(datum['team_id'][0], 0)
            counts[datum['team_id'][0]] += 1
            amounts[datum['team_id'][0]] += (datum.get('planned_revenue', 0) *
                                             datum.get('probability', 0) /
                                             100.0)
        for team in self:
            team.opportunities_count = counts.get(team.id, 0)
            team.opportunities_amount = amounts.get(team.id, 0)

    def _compute_dashboard_graph_period_pipeline(self):
        for channel in self:
            channel.dashboard_graph_period_pipeline = channel.dashboard_graph_period

    def _inverse_dashboard_graph_period_pipeline(self):
        for channel in self.filtered(
                lambda ch: ch.dashboard_graph_model == 'crm.lead'):
            channel.dashboard_graph_period = channel.dashboard_graph_period_pipeline

    def get_alias_model_name(self, vals):
        return 'crm.lead'

    def get_alias_values(self):
        has_group_use_lead = self.env.user.has_group('crm.group_use_lead')
        values = super(Team, self).get_alias_values()
        values['alias_defaults'] = defaults = safe_eval(self.alias_defaults
                                                        or "{}")
        defaults[
            'type'] = 'lead' if has_group_use_lead and self.use_leads else 'opportunity'
        defaults['team_id'] = self.id
        return values

    @api.onchange('use_leads', 'use_opportunities')
    def _onchange_use_leads_opportunities(self):
        if not self.use_leads and not self.use_opportunities:
            self.alias_name = False
        if not self.use_opportunities and self.use_leads:
            self.use_leads = False

    @api.onchange('team_type')
    def _onchange_team_type(self):
        if self.team_type == 'sales':
            self.use_opportunities = True
            self.use_leads = lambda self: self.user_has_groups(
                'crm.group_use_lead')
            self.dashboard_graph_model = 'crm.lead'
        else:
            self.use_opportunities = False
            self.use_leads = False
        return super(Team, self)._onchange_team_type()

    @api.onchange('dashboard_graph_model')
    def _onchange_dashboard_graph_model(self):
        if self.dashboard_graph_model == 'crm.lead':
            self.dashboard_graph_period_pipeline = self.dashboard_graph_period
            self.dashboard_graph_group_pipeline = self.dashboard_graph_group
        else:
            self.dashboard_graph_period = self.dashboard_graph_period_pipeline
            if not self.dashboard_graph_group:
                self.dashboard_graph_group = self._fields[
                    'dashboard_graph_group'].default(self)

    @api.onchange('dashboard_graph_group_pipeline')
    def _onchange_dashboard_graph_group_pipeline(self):
        if self.dashboard_graph_group_pipeline == 'stage':
            self.dashboard_graph_group = False
        else:
            self.dashboard_graph_group = self.dashboard_graph_group_pipeline

    @api.constrains('dashboard_graph_model', 'use_opportunities')
    def _check_graph_model(self):
        if not self.use_opportunities and self.dashboard_graph_model == 'crm.lead':
            raise ValidationError(
                _("You have to enable the Pipeline on your Sales Team to be able to set it as a content for the graph"
                  ))

    @api.multi
    def write(self, vals):
        result = super(Team, self).write(vals)
        if 'use_leads' in vals or 'alias_defaults' in vals:
            for team in self:
                team.alias_id.write(team.get_alias_values())
        return result

    #TODO JEM : refactor this stuff with xml action, proper customization,
    @api.model
    def action_your_pipeline(self):
        action = self.env.ref('crm.crm_lead_opportunities_tree_view').read()[0]
        user_team_id = self.env.user.sale_team_id.id
        if user_team_id:
            # To ensure that the team is readable in multi company
            user_team_id = self.search([('id', '=', user_team_id)], limit=1).id
        else:
            user_team_id = self.search([], limit=1).id
            action['help'] = _(
                """<p class='o_view_nocontent_smiling_face'>Add new opportunities</p><p>
    Looks like you are not a member of a Sales Team. You should add yourself
    as a member of one of the Sales Team.
</p>""")
            if user_team_id:
                action[
                    'help'] += "<p>As you don't belong to any Sales Team, Swerp opens the first one by default.</p>"

        action_context = safe_eval(action['context'], {'uid': self.env.uid})
        if user_team_id:
            action_context['default_team_id'] = user_team_id

        action['context'] = action_context
        return action

    def _compute_dashboard_button_name(self):
        opportunity_teams = self.filtered('use_opportunities')
        opportunity_teams.update({'dashboard_button_name': _("Pipeline")})
        super(Team, self - opportunity_teams)._compute_dashboard_button_name()

    def action_primary_channel_button(self):
        if self.use_opportunities:
            action = self.env.ref(
                'crm.crm_case_form_view_salesteams_opportunity').read()[0]
            return action
        return super(Team, self).action_primary_channel_button()

    def _graph_get_dates(self, today):
        """ return a coherent start and end date for the dashboard graph according to the graph settings.
        """
        if self.dashboard_graph_model == 'crm.lead':
            if self.dashboard_graph_group == 'month':
                start_date = today.replace(day=1)
            elif self.dashboard_graph_group == 'week':
                start_date = today - relativedelta(
                    days=today.isocalendar()[2] - 1)
            else:
                start_date = today

            if self.dashboard_graph_period == 'week':
                end_date = today + relativedelta(weeks=1)
            elif self.dashboard_graph_period == 'year':
                end_date = today + relativedelta(years=1)
            else:
                end_date = today + relativedelta(months=1)

            # we take the end of the preceding month/week/day if we group by month/week/day
            # (to avoid having twice the same month/week/day from different years/month/week)
            if self.dashboard_graph_group == 'month':
                end_date = end_date.replace(day=1) - relativedelta(days=1)
            elif self.dashboard_graph_group == 'week':
                end_date -= relativedelta(days=end_date.isocalendar()[2])
            else:
                end_date -= relativedelta(days=1)

            return [start_date, end_date]
        return super(Team, self)._graph_get_dates(today)

    def _get_graph(self):
        graph_datas = super(Team, self)._get_graph()
        if self.dashboard_graph_model == 'crm.lead' and self.dashboard_graph_group_pipeline == 'stage':
            stage_ids = [
                d['label'] for d in graph_datas[0]['values']
                if d['label'] is not None
            ]
            stage_data = self.env['crm.stage'].browse(stage_ids).read(
                ['sequence', 'name'])
            stage_data = {
                d['id']: {
                    'name': d['name'],
                    'sequence': d['sequence']
                }
                for d in stage_data
            }
            # use "Undefined" stage for unset stage records
            stage_data[None] = {'name': _('Undefined'), 'sequence': -1}
            graph_datas[0]['values'] = sorted(
                graph_datas[0]['values'],
                key=lambda el: stage_data[el['label']]['sequence'])
            for gdata in graph_datas[0]['values']:
                gdata['label'] = stage_data[gdata['label']]['name']
        return graph_datas

    def _graph_date_column(self):
        if self.dashboard_graph_model == 'crm.lead':
            return 'date_deadline'
        return super(Team, self)._graph_date_column()

    def _graph_x_query(self):
        if self.dashboard_graph_model == 'crm.lead' and self.dashboard_graph_group_pipeline == 'stage':
            return 'stage_id'
        return super(Team, self)._graph_x_query()

    def _graph_y_query(self):
        if self.dashboard_graph_model == 'crm.lead':
            return 'SUM(expected_revenue)'
        return super(Team, self)._graph_y_query()

    def _graph_title_and_key(self):
        if self.dashboard_graph_model == 'crm.lead':
            return ['', _('Pipeline: Expected Revenue')]  # no more title
        return super(Team, self)._graph_title_and_key()
Beispiel #21
0
class MaintenanceEquipment(models.Model):
    _inherit = 'maintenance.equipment'

    employee_id = fields.Many2one('hr.employee', string='Assigned to Employee', track_visibility='onchange')
    department_id = fields.Many2one('hr.department', string='Assigned to Department', track_visibility='onchange')
    equipment_assign_to = fields.Selection(
        [('department', 'Department'), ('employee', 'Employee'), ('other', 'Other')],
        string='Used By',
        required=True,
        default='employee')
    owner_user_id = fields.Many2one(compute='_compute_owner', store=True)

    @api.one
    @api.depends('employee_id', 'department_id', 'equipment_assign_to')
    def _compute_owner(self):
        self.owner_user_id = self.env.user.id
        if self.equipment_assign_to == 'employee':
            self.owner_user_id = self.employee_id.user_id.id
        elif self.equipment_assign_to == 'department':
            self.owner_user_id = self.department_id.manager_id.user_id.id

    @api.onchange('equipment_assign_to')
    def _onchange_equipment_assign_to(self):
        if self.equipment_assign_to == 'employee':
            self.department_id = False
        if self.equipment_assign_to == 'department':
            self.employee_id = False
        self.assign_date = fields.Date.context_today(self)

    @api.model
    def create(self, vals):
        equipment = super(MaintenanceEquipment, self).create(vals)
        # subscribe employee or department manager when equipment assign to him.
        partner_ids = []
        if equipment.employee_id and equipment.employee_id.user_id:
            partner_ids.append(equipment.employee_id.user_id.partner_id.id)
        if equipment.department_id and equipment.department_id.manager_id and equipment.department_id.manager_id.user_id:
            partner_ids.append(equipment.department_id.manager_id.user_id.partner_id.id)
        if partner_ids:
            equipment.message_subscribe(partner_ids=partner_ids)
        return equipment

    @api.multi
    def write(self, vals):
        partner_ids = []
        # subscribe employee or department manager when equipment assign to employee or department.
        if vals.get('employee_id'):
            user_id = self.env['hr.employee'].browse(vals['employee_id'])['user_id']
            if user_id:
                partner_ids.append(user_id.partner_id.id)
        if vals.get('department_id'):
            department = self.env['hr.department'].browse(vals['department_id'])
            if department and department.manager_id and department.manager_id.user_id:
                partner_ids.append(department.manager_id.user_id.partner_id.id)
        if partner_ids:
            self.message_subscribe(partner_ids=partner_ids)
        return super(MaintenanceEquipment, self).write(vals)

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if ('employee_id' in init_values and self.employee_id) or ('department_id' in init_values and self.department_id):
            return 'maintenance.mt_mat_assign'
        return super(MaintenanceEquipment, self)._track_subtype(init_values)