示例#1
0
    def test_predictive_lead_scoring(self):
        """ We test here computation of lead probability based on PLS Bayes.
                We will use 3 different values for each possible variables:
                country_id : 1,2,3
                state_id: 1,2,3
                email_state: correct, incorrect, None
                phone_state: correct, incorrect, None
                source_id: 1,2,3
                stage_id: 1,2,3 + the won stage
                And we will compute all of this for 2 different team_id
            Note : We assume here that original bayes computation is correct
            as we don't compute manually the probabilities."""
        Lead = self.env['crm.lead']
        state_values = ['correct', 'incorrect', None]
        source_ids = self.env['utm.source'].search([], limit=3).ids
        state_ids = self.env['res.country.state'].search([], limit=3).ids
        country_ids = self.env['res.country'].search([], limit=3).ids
        stage_ids = self.env['crm.stage'].search([], limit=3).ids
        team_ids = self.env['crm.team'].create([{'name': 'Team Test 1'}, {'name': 'Team Test 2'}]).ids
        # create bunch of lost and won crm_lead
        leads_to_create = []
        #   for team 1
        for i in range(3):
            leads_to_create.append(self._get_lead_values(team_ids[0], 'team_1_%s' % str(i), country_ids[i], state_ids[i], state_values[i], state_values[i], source_ids[i], stage_ids[i]))
        leads_to_create.append(
            self._get_lead_values(team_ids[0], 'team_1_%s' % str(3), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[2], stage_ids[1]))
        #   for team 2
        leads_to_create.append(
            self._get_lead_values(team_ids[1], 'team_2_%s' % str(0), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[1], stage_ids[2]))
        leads_to_create.append(
            self._get_lead_values(team_ids[1], 'team_2_%s' % str(1), country_ids[0], state_ids[1], state_values[0], state_values[1], source_ids[2], stage_ids[1]))
        leads_to_create.append(
            self._get_lead_values(team_ids[1], 'team_2_%s' % str(2), country_ids[0], state_ids[2], state_values[0], state_values[1], source_ids[2], stage_ids[0]))
        leads_to_create.append(
            self._get_lead_values(team_ids[1], 'team_2_%s' % str(3), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[2], stage_ids[1]))

        leads = Lead.create(leads_to_create)

        # Set the PLS config
        self.env['ir.config_parameter'].sudo().set_param("crm.pls_start_date", "2000-01-01")
        self.env['ir.config_parameter'].sudo().set_param("crm.pls_fields", "country_id,state_id,email_state,phone_state,source_id")

        # set leads as won and lost
        # for Team 1
        leads[0].action_set_lost()
        leads[1].action_set_lost()
        leads[2].action_set_won()
        # for Team 2
        leads[4].action_set_lost()
        leads[5].action_set_lost()
        leads[6].action_set_won()

        # rebuild frequencies table and recompute automated_probability for all leads.
        Lead._cron_update_automated_probabilities()

        # As the cron is computing and writing in SQL queries, we need to invalidate the cache
        leads.invalidate_cache()

        self.assertEquals(tools.float_compare(leads[3].automated_probability, 33.49, 2), 0)
        self.assertEquals(tools.float_compare(leads[7].automated_probability, 7.74, 2), 0)
示例#2
0
    def _update_move_lines(self):
        """ update a move line to save the workorder line data"""
        self.ensure_one()
        if self.lot_id:
            move_lines = self.move_id.move_line_ids.filtered(
                lambda ml: ml.lot_id == self.lot_id and not ml.lot_produced_ids
            )
        else:
            move_lines = self.move_id.move_line_ids.filtered(
                lambda ml: not ml.lot_id and not ml.lot_produced_ids)

        # Sanity check: if the product is a serial number and `lot` is already present in the other
        # consumed move lines, raise.
        if self.product_id.tracking != 'none' and not self.lot_id:
            raise UserError(
                _('Please enter a lot or serial number for %s !' %
                  self.product_id.display_name))

        if self.lot_id and self.product_id.tracking == 'serial' and self.lot_id in self.move_id.move_line_ids.filtered(
                lambda ml: ml.qty_done).mapped('lot_id'):
            raise UserError(
                _('You cannot consume the same serial number twice. Please correct the serial numbers encoded.'
                  ))

        # Update reservation and quantity done
        for ml in move_lines:
            rounding = ml.product_uom_id.rounding
            if float_compare(self.qty_done, 0,
                             precision_rounding=rounding) <= 0:
                break
            quantity_to_process = min(self.qty_done,
                                      ml.product_uom_qty - ml.qty_done)
            self.qty_done -= quantity_to_process

            new_quantity_done = (ml.qty_done + quantity_to_process)
            # if we produce less than the reserved quantity to produce the finished products
            # in different lots,
            # we create different component_move_lines to record which one was used
            # on which lot of finished product
            if float_compare(new_quantity_done,
                             ml.product_uom_qty,
                             precision_rounding=rounding) >= 0:
                ml.write({
                    'qty_done': new_quantity_done,
                    'lot_produced_ids': self._get_produced_lots(),
                })
            else:
                new_qty_reserved = ml.product_uom_qty - new_quantity_done
                default = {
                    'product_uom_qty': new_quantity_done,
                    'qty_done': new_quantity_done,
                    'lot_produced_ids': self._get_produced_lots(),
                }
                ml.copy(default=default)
                ml.with_context(bypass_reservation_update=True).write({
                    'product_uom_qty':
                    new_qty_reserved,
                    'qty_done':
                    0
                })
示例#3
0
 def _start_nextworkorder(self):
     rounding = self.product_id.uom_id.rounding
     if self.next_work_order_id.state == 'pending' and (
         (self.operation_id.batch == 'no'
          and float_compare(self.qty_production,
                            self.qty_produced,
                            precision_rounding=rounding) <= 0) or
         (self.operation_id.batch == 'yes'
          and float_compare(self.operation_id.batch_size,
                            self.qty_produced,
                            precision_rounding=rounding) <= 0)):
         self.next_work_order_id.state = 'ready'
示例#4
0
 def _check_package(self):
     default_uom = self.product_id.uom_id
     pack = self.product_packaging
     qty = self.product_uom_qty
     q = default_uom._compute_quantity(pack.qty, self.product_uom)
     # We do not use the modulo operator to check if qty is a mltiple of q. Indeed the quantity
     # per package might be a float, leading to incorrect results. For example:
     # 8 % 1.6 = 1.5999999999999996
     # 5.4 % 1.8 = 2.220446049250313e-16
     if (qty and q
             and float_compare(qty / q,
                               float_round(qty / q, precision_rounding=1.0),
                               precision_rounding=0.001) != 0):
         newqty = qty - (qty % q) + q
         return {
             'warning': {
                 'title':
                 _('Warning'),
                 'message':
                 _("This product is packaged by %.2f %s. You should sell %.2f %s."
                   ) % (pack.qty, default_uom.name, newqty,
                        self.product_uom.name),
             },
         }
     return {}
示例#5
0
 def _sale_can_be_reinvoice(self):
     """ determine if the generated analytic line should be reinvoiced or not.
         For Vendor Bill flow, if the product has a 'erinvoice policy' and is a cost, then we will find the SO on which reinvoice the AAL
     """
     self.ensure_one()
     uom_precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure')
     return float_compare(self.credit or 0.0, self.debit or 0.0, precision_digits=uom_precision_digits) != 1 and self.product_id.expense_policy not in [False, 'no']
示例#6
0
 def action_validate(self):
     self.ensure_one()
     if self.product_id.type != 'product':
         return self.do_scrap()
     precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
     location_id = self.location_id
     if self.picking_id and self.picking_id.picking_type_code == 'incoming':
         location_id = self.picking_id.location_dest_id
     available_qty = sum(self.env['stock.quant']._gather(self.product_id,
                                                         location_id,
                                                         self.lot_id,
                                                         self.package_id,
                                                         self.owner_id,
                                                         strict=True).mapped('quantity'))
     scrap_qty = self.product_uom_id._compute_quantity(self.scrap_qty, self.product_id.uom_id)
     if float_compare(available_qty, scrap_qty, precision_digits=precision) >= 0:
         return self.do_scrap()
     else:
         ctx = dict(self.env.context)
         ctx.update({
             'default_product_id': self.product_id.id,
             'default_location_id': self.location_id.id,
             'default_scrap_id': self.id
         })
         return {
             'name': _('Insufficient Quantity'),
             'view_mode': 'form',
             'res_model': 'stock.warn.insufficient.qty.scrap',
             'view_id': self.env.ref('stock.stock_warn_insufficient_qty_scrap_form_view').id,
             'type': 'ir.actions.act_window',
             'context': ctx,
             'target': 'new'
         }
示例#7
0
 def _compute_is_produced(self):
     self.is_produced = False
     for order in self.filtered(lambda p: p.production_id):
         rounding = order.production_id.product_uom_id.rounding
         order.is_produced = float_compare(order.qty_produced,
                                           order.production_id.product_qty,
                                           precision_rounding=rounding) >= 0
示例#8
0
 def action_validate(self):
     self.ensure_one()
     precision = self.env['decimal.precision'].precision_get(
         'Product Unit of Measure')
     available_qty = self.env['stock.quant']._get_available_quantity(
         self.product_id, self.location_id, self.lot_id, strict=True)
     if float_compare(available_qty,
                      self.product_qty,
                      precision_digits=precision) >= 0:
         return self.action_unbuild()
     else:
         return {
             'name':
             _('Insufficient Quantity'),
             'view_mode':
             'form',
             'res_model':
             'stock.warn.insufficient.qty.unbuild',
             'view_id':
             self.env.ref(
                 'mrp.stock_warn_insufficient_qty_unbuild_form_view').id,
             'type':
             'ir.actions.act_window',
             'context': {
                 'default_product_id': self.product_id.id,
                 'default_location_id': self.location_id.id,
                 'default_unbuild_id': self.id
             },
             'target':
             'new'
         }
示例#9
0
    def _refresh_wo_lines(self):
        """ Modify exisiting workorder line in order to match the reservation on
        stock move line. The strategy is to remove the line that were not
        processed yet then call _generate_lines_values that recreate workorder
        line depending the reservation.
        """
        for workorder in self:
            raw_moves = workorder.move_raw_ids.filtered(
                lambda move: move.state not in ('done', 'cancel'))
            wl_to_unlink = self.env['mrp.workorder.line']
            for move in raw_moves:
                rounding = move.product_uom.rounding
                qty_already_consumed = 0.0
                workorder_lines = workorder.raw_workorder_line_ids.filtered(
                    lambda w: w.move_id == move)
                for wl in workorder_lines:
                    if not wl.qty_done:
                        wl_to_unlink |= wl
                        continue

                    qty_already_consumed += wl.qty_done
                qty_to_consume = self._prepare_component_quantity(
                    move, workorder.qty_producing)
                wl_to_unlink.unlink()
                if float_compare(qty_to_consume,
                                 qty_already_consumed,
                                 precision_rounding=rounding) > 0:
                    line_values = workorder._generate_lines_values(
                        move, qty_to_consume - qty_already_consumed)
                    self.env['mrp.workorder.line'].create(line_values)
示例#10
0
 def _check_amount_and_confirm_order(self):
     self.ensure_one()
     for order in self.sale_order_ids.filtered(lambda so: so.state in
                                               ('draft', 'sent')):
         if float_compare(self.amount, order.amount_total, 2) == 0:
             order.with_context(send_email=True).action_confirm()
         else:
             _logger.warning(
                 '<%s> transaction AMOUNT MISMATCH for order %s (ID %s): expected %r, got %r',
                 self.acquirer_id.provider,
                 order.name,
                 order.id,
                 order.amount_total,
                 self.amount,
             )
             order.message_post(
                 subject=_("Amount Mismatch (%s)") %
                 self.acquirer_id.provider,
                 body=
                 _("The order was not confirmed despite response from the acquirer (%s): order total is %r but acquirer replied with %r."
                   ) % (
                       self.acquirer_id.provider,
                       order.amount_total,
                       self.amount,
                   ))
示例#11
0
 def _defaults_from_finished_workorder_line(self, reference_lot_lines):
     for r_line in reference_lot_lines:
         # see which lot we could suggest and its related qty_producing
         if not r_line.lot_id:
             continue
         candidates = self.finished_workorder_line_ids.filtered(
             lambda line: line.lot_id == r_line.lot_id)
         rounding = self.product_uom_id.rounding
         if not candidates:
             self.write({
                 'finished_lot_id': r_line.lot_id.id,
                 'qty_producing': r_line.qty_done,
             })
             return True
         elif float_compare(candidates.qty_done,
                            r_line.qty_done,
                            precision_rounding=rounding) < 0:
             self.write({
                 'finished_lot_id':
                 r_line.lot_id.id,
                 'qty_producing':
                 r_line.qty_done - candidates.qty_done,
             })
             return True
     return False
示例#12
0
    def _onchange_quantity_context(self):
        product_qty = False
        if self.product_id:
            self.product_uom_id = self.product_id.uom_id
        if self.product_id and self.location_id and self.product_id.uom_id.category_id == self.product_uom_id.category_id:  # TDE FIXME: last part added because crash
            theoretical_qty = self.product_id.get_theoretical_quantity(
                self.product_id.id,
                self.location_id.id,
                lot_id=self.prod_lot_id.id,
                package_id=self.package_id.id,
                owner_id=self.partner_id.id,
                to_uom=self.product_uom_id.id,
            )
        else:
            theoretical_qty = 0
        # Sanity check on the lot.
        if self.prod_lot_id:
            if self.product_id.tracking == 'none' or self.product_id != self.prod_lot_id.product_id:
                self.prod_lot_id = False

        if self.prod_lot_id and self.product_id.tracking == 'serial':
            # We force `product_qty` to 1 for SN tracked product because it's
            # the only relevant value aside 0 for this kind of product.
            self.product_qty = 1
        elif self.product_id and float_compare(
                self.product_qty,
                self.theoretical_qty,
                precision_rounding=self.product_uom_id.rounding) == 0:
            # We update `product_qty` only if it equals to `theoretical_qty` to
            # avoid to reset quantity when user manually set it.
            self.product_qty = theoretical_qty
        self.theoretical_qty = theoretical_qty
示例#13
0
 def _compute_outdated(self):
     grouped_quants = self.env['stock.quant'].read_group(
         [('product_id', 'in', self.product_id.ids),
          ('location_id', 'in', self.location_id.ids)], [
              'product_id', 'location_id', 'lot_id', 'package_id',
              'owner_id', 'quantity:sum'
          ],
         ['product_id', 'location_id', 'lot_id', 'package_id', 'owner_id'],
         lazy=False)
     quants = {(quant['product_id'][0], quant['location_id'][0],
                quant['lot_id'] and quant['lot_id'][0], quant['package_id']
                and quant['package_id'][0], quant['owner_id']
                and quant['owner_id'][0]): quant['quantity']
               for quant in grouped_quants}
     for line in self:
         if line.state == 'done' or not line.id:
             line.outdated = False
             continue
         qty = quants.get((
             line.product_id.id,
             line.location_id.id,
             line.prod_lot_id.id,
             line.package_id.id,
             line.partner_id.id,
         ), 0)
         if float_compare(
                 qty,
                 line.theoretical_qty,
                 precision_rounding=line.product_uom_id.rounding) != 0:
             line.outdated = True
         else:
             line.outdated = False
示例#14
0
 def write(self, values):
     res = super().write(values)
     for bom in self:
         if float_compare(
                 bom.product_qty,
                 0,
                 precision_rounding=bom.product_uom_id.rounding) <= 0:
             raise UserError(_('The quantity to produce must be positive!'))
     return res
示例#15
0
 def _check_main_currency_rounding(self):
     if any(precision.name == 'Account'
            and tools.float_compare(self.env.company.currency_id.rounding,
                                    10**-precision.digits,
                                    precision_digits=6) == -1
            for precision in self):
         raise ValidationError(
             _("You cannot define the decimal precision of 'Account' as greater than the rounding factor of the company's main currency"
               ))
     return True
示例#16
0
 def _onchange_amount(self):
     if float_compare(self.amount_max,
                      self.amount,
                      precision_rounding=self.currency_id.rounding
                      or 0.01) == -1:
         raise ValidationError(
             _("Please set an amount smaller than %s.") % (self.amount_max))
     if self.amount <= 0:
         raise ValidationError(
             _("The value of the payment amount must be positive."))
示例#17
0
    def _prepare_reconciliation(self, st_line, move_lines=None, partner=None):
        ''' Reconcile the statement line with some move lines using this reconciliation model.
        :param st_line:     An account.bank.statement.line record.
        :param move_lines:  An account.move.line recordset.
        :param partner_id:  An optional res.partner record. If not set, st_line.partner_id will be used.
        :return:            Counterpart account.moves.
        '''
        self.ensure_one()

        # Create counterpart_aml_dicts + payment_aml_rec.
        counterpart_aml_dicts = []
        payment_aml_rec = self.env['account.move.line']
        if move_lines:
            for aml in move_lines:
                if aml.account_id.internal_type == 'liquidity':
                    payment_aml_rec |= aml
                else:
                    amount = aml.currency_id and aml.amount_residual_currency or aml.amount_residual
                    counterpart_aml_dicts.append({
                        'name': aml.name if aml.name != '/' else aml.move_id.name,
                        'debit': amount < 0 and -amount or 0,
                        'credit': amount > 0 and amount or 0,
                        'move_line': aml,
                    })

        # Create new_aml_dicts.
        new_aml_dicts = self._get_write_off_move_lines_dict(st_line, move_lines=move_lines)

        line_residual = st_line.currency_id and st_line.amount_currency or st_line.amount
        line_currency = st_line.currency_id or st_line.journal_id.currency_id or st_line.company_id.currency_id
        total_residual = move_lines and sum(aml.currency_id and aml.amount_residual_currency or aml.amount_residual for aml in move_lines) or 0.0
        total_residual -= sum(aml['debit'] - aml['credit'] for aml in new_aml_dicts)

        # Create open_balance_dict
        open_balance_dict = None
        if float_compare(line_residual, total_residual, precision_rounding=line_currency.rounding) != 0:
            if not partner and not st_line.partner_id:
                open_balance_dict = False
            else:
                balance = total_residual - line_residual
                partner = partner or st_line.partner_id
                open_balance_dict = {
                    'name': '%s : %s' % (st_line.name, _('Open Balance')),
                    'account_id': balance < 0 and partner.property_account_payable_id.id or partner.property_account_receivable_id.id,
                    'debit': balance > 0 and balance or 0,
                    'credit': balance < 0 and -balance or 0,
                }
        return {
           'counterpart_aml_dicts': counterpart_aml_dicts,
           'payment_aml_rec': payment_aml_rec,
           'new_aml_dicts': new_aml_dicts,
           'open_balance_dict': open_balance_dict
        }
示例#18
0
    def test_rounding_invalid(self):
        """ verify that invalid parameters are forbidden """
        with self.assertRaises(AssertionError):
            float_is_zero(0.01, precision_digits=3, precision_rounding=0.01)

        with self.assertRaises(AssertionError):
            float_is_zero(0.0, precision_rounding=0.0)

        with self.assertRaises(AssertionError):
            float_is_zero(0.0, precision_rounding=-0.1)

        with self.assertRaises(AssertionError):
            float_compare(0.01,
                          0.02,
                          precision_digits=3,
                          precision_rounding=0.01)

        with self.assertRaises(AssertionError):
            float_compare(1.0, 1.0, precision_rounding=0.0)

        with self.assertRaises(AssertionError):
            float_compare(1.0, 1.0, precision_rounding=-0.1)

        with self.assertRaises(AssertionError):
            float_round(0.01, precision_digits=3, precision_rounding=0.01)

        with self.assertRaises(AssertionError):
            float_round(1.25, precision_rounding=0.0)

        with self.assertRaises(AssertionError):
            float_round(1.25, precision_rounding=-0.1)
示例#19
0
    def _action_launch_stock_rule(self, previous_product_uom_qty=False):
        """
        Launch procurement group run method with required/custom fields genrated by a
        sale order line. procurement group will launch '_run_pull', '_run_buy' or '_run_manufacture'
        depending on the sale order line product rule.
        """
        precision = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        procurements = []
        for line in self:
            if line.state != 'sale' or not line.product_id.type in ('consu',
                                                                    'product'):
                continue
            qty = line._get_qty_procurement(previous_product_uom_qty)
            if float_compare(qty,
                             line.product_uom_qty,
                             precision_digits=precision) >= 0:
                continue

            group_id = line._get_procurement_group()
            if not group_id:
                group_id = self.env['procurement.group'].create(
                    line._prepare_procurement_group_vals())
                line.order_id.procurement_group_id = group_id
            else:
                # In case the procurement group is already created and the order was
                # cancelled, we need to update certain values of the group.
                updated_vals = {}
                if group_id.partner_id != line.order_id.partner_shipping_id:
                    updated_vals.update(
                        {'partner_id': line.order_id.partner_shipping_id.id})
                if group_id.move_type != line.order_id.picking_policy:
                    updated_vals.update(
                        {'move_type': line.order_id.picking_policy})
                if updated_vals:
                    group_id.write(updated_vals)

            values = line._prepare_procurement_values(group_id=group_id)
            product_qty = line.product_uom_qty - qty

            line_uom = line.product_uom
            quant_uom = line.product_id.uom_id
            product_qty, procurement_uom = line_uom._adjust_uom_quantities(
                product_qty, quant_uom)
            procurements.append(self.env['procurement.group'].Procurement(
                line.product_id, product_qty, procurement_uom,
                line.order_id.partner_shipping_id.property_stock_customer,
                line.name, line.order_id.name, line.order_id.company_id,
                values))
        if procurements:
            self.env['procurement.group'].run(procurements)
        return True
示例#20
0
 def _update_line_quantity(self, values):
     precision = self.env['decimal.precision'].precision_get(
         'Product Unit of Measure')
     line_products = self.filtered(
         lambda l: l.product_id.type in ['product', 'consu'])
     if line_products.mapped('qty_delivered') and float_compare(
             values['product_uom_qty'],
             max(line_products.mapped('qty_delivered')),
             precision_digits=precision) == -1:
         raise UserError(
             _('You cannot decrease the ordered quantity below the delivered quantity.\n'
               'Create a return first.'))
     super(SaleOrderLine, self)._update_line_quantity(values)
示例#21
0
    def _compute_allowed_lots_domain(self):
        """ Check if all the finished products has been assigned to a serial
        number or a lot in other workorders. If yes, restrict the selectable lot
        to the lot/sn used in other workorders.
        """
        productions = self.mapped('production_id')
        treated = self.browse()
        for production in productions:
            if production.product_id.tracking == 'none':
                continue

            rounding = production.product_uom_id.rounding
            finished_workorder_lines = production.workorder_ids.mapped(
                'finished_workorder_line_ids').filtered(
                    lambda wl: wl.product_id == production.product_id)
            qties_done_per_lot = defaultdict(list)
            for finished_workorder_line in finished_workorder_lines:
                # It is possible to have finished workorder lines without a lot (eg using the dummy
                # test type). Ignore them when computing the allowed lots.
                if finished_workorder_line.lot_id:
                    qties_done_per_lot[
                        finished_workorder_line.lot_id.id].append(
                            finished_workorder_line.qty_done)

            qty_to_produce = production.product_qty
            allowed_lot_ids = self.env['stock.production.lot']
            qty_produced = sum(
                [max(qty_dones) for qty_dones in qties_done_per_lot.values()])
            if float_compare(qty_produced,
                             qty_to_produce,
                             precision_rounding=rounding) < 0:
                # If we haven't produced enough, all lots are available
                allowed_lot_ids = self.env['stock.production.lot'].search([
                    ('product_id', '=', production.product_id.id),
                    ('company_id', '=', production.company_id.id),
                ])
            else:
                # If we produced enough, only the already produced lots are available
                allowed_lot_ids = self.env['stock.production.lot'].browse(
                    qties_done_per_lot.keys())
            workorders = production.workorder_ids.filtered(
                lambda wo: wo.state not in ('done', 'cancel'))
            for workorder in workorders:
                if workorder.product_tracking == 'serial':
                    workorder.allowed_lots_domain = allowed_lot_ids - workorder.finished_workorder_line_ids.filtered(
                        lambda wl: wl.product_id == production.product_id
                    ).mapped('lot_id')
                else:
                    workorder.allowed_lots_domain = allowed_lot_ids
                treated |= workorder
        (self - treated).allowed_lots_domain = False
示例#22
0
    def write(self, values):
        increased_lines = None
        decreased_lines = None
        increased_values = {}
        decreased_values = {}
        if 'product_uom_qty' in values:
            precision = self.env['decimal.precision'].precision_get(
                'Product Unit of Measure')
            increased_lines = self.sudo().filtered(
                lambda r: r.product_id.service_to_purchase and r.
                purchase_line_count and float_compare(
                    r.product_uom_qty,
                    values['product_uom_qty'],
                    precision_digits=precision) == -1)
            decreased_lines = self.sudo().filtered(
                lambda r: r.product_id.service_to_purchase and r.
                purchase_line_count and float_compare(
                    r.product_uom_qty,
                    values['product_uom_qty'],
                    precision_digits=precision) == 1)
            increased_values = {
                line.id: line.product_uom_qty
                for line in increased_lines
            }
            decreased_values = {
                line.id: line.product_uom_qty
                for line in decreased_lines
            }

        result = super(SaleOrderLine, self).write(values)

        if increased_lines:
            increased_lines._purchase_increase_ordered_qty(
                values['product_uom_qty'], increased_values)
        if decreased_lines:
            decreased_lines._purchase_decrease_ordered_qty(
                values['product_uom_qty'], decreased_values)
        return result
示例#23
0
 def _reservation_is_updatable(self, quantity, reserved_quant):
     self.ensure_one()
     if self.lot_produced_ids:
         ml_remaining_qty = self.qty_done - self.product_uom_qty
         ml_remaining_qty = self.product_uom_id._compute_quantity(
             ml_remaining_qty,
             self.product_id.uom_id,
             rounding_method="HALF-UP")
         if float_compare(
                 ml_remaining_qty,
                 quantity,
                 precision_rounding=self.product_id.uom_id.rounding) < 0:
             return False
     return super(StockMoveLine,
                  self)._reservation_is_updatable(quantity, reserved_quant)
示例#24
0
 def _onchange_qty_done(self):
     """ When the user is encoding a produce line for a tracked product, we apply some logic to
     help him. This onchange will warn him if he set `qty_done` to a non-supported value.
     """
     res = {}
     if self.product_id.tracking == 'serial' and not float_is_zero(
             self.qty_done, self.product_uom_id.rounding):
         if float_compare(
                 self.qty_done,
                 1.0,
                 precision_rounding=self.product_uom_id.rounding) != 0:
             message = _(
                 'You can only process 1.0 %s of products with unique serial number.'
             ) % self.product_id.uom_id.name
             res['warning'] = {'title': _('Warning'), 'message': message}
     return res
示例#25
0
    def _record_production(self):
        # Check all the product_produce line have a move id (the user can add product
        # to consume directly in the wizard)
        for line in self._workorder_line_ids():
            if not line.move_id:
                # Find move_id that would match
                if line.raw_product_produce_id:
                    moves = self.move_raw_ids
                else:
                    moves = self.move_finished_ids
                move_id = moves.filtered(lambda m: m.product_id == line.product_id and m.state not in ('done', 'cancel'))
                if not move_id:
                    # create a move to assign it to the line
                    if line.raw_product_produce_id:
                        values = {
                            'name': self.production_id.name,
                            'reference': self.production_id.name,
                            'product_id': line.product_id.id,
                            'product_uom': line.product_uom_id.id,
                            'location_id': self.production_id.location_src_id.id,
                            'location_dest_id': line.product_id.property_stock_production.id,
                            'raw_material_production_id': self.production_id.id,
                            'group_id': self.production_id.procurement_group_id.id,
                            'origin': self.production_id.name,
                            'state': 'confirmed',
                            'company_id': self.production_id.company_id.id,
                        }
                    else:
                        values = self.production_id._get_finished_move_value(line.product_id.id, 0, line.product_uom_id.id)
                    move_id = self.env['stock.move'].create(values)
                line.move_id = move_id.id

        # because of an ORM limitation (fields on transient models are not
        # recomputed by updates in non-transient models), the related fields on
        # this model are not recomputed by the creations above
        self.invalidate_cache(['move_raw_ids', 'move_finished_ids'])

        # Save product produce lines data into stock moves/move lines
        quantity = self.qty_producing
        if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0:
            raise UserError(_("The production order for '%s' has no quantity specified.") % self.product_id.display_name)
        self._update_finished_move()
        self._update_moves()
        if self.production_id.state == 'confirmed':
            self.production_id.write({
                'date_start': datetime.now(),
            })
示例#26
0
    def write(self, values):
        if values.get('order_line') and self.state == 'sale':
            for order in self:
                pre_order_line_qty = {
                    order_line: order_line.product_uom_qty
                    for order_line in order.mapped('order_line')
                    if not order_line.is_expense
                }

        if values.get('partner_shipping_id'):
            new_partner = self.env['res.partner'].browse(
                values.get('partner_shipping_id'))
            for record in self:
                picking = record.mapped('picking_ids').filtered(
                    lambda x: x.state not in ('done', 'cancel'))
                addresses = (record.partner_shipping_id.display_name,
                             new_partner.display_name)
                message = _(
                    """The delivery address has been changed on the Sales Order<br/>
                        From <strong>"%s"</strong> To <strong>"%s"</strong>,
                        You should probably update the partner on this document."""
                ) % addresses
                picking.activity_schedule('mail.mail_activity_data_warning',
                                          note=message,
                                          user_id=self.env.user.id)

        res = super(SaleOrder, self).write(values)
        if values.get('order_line') and self.state == 'sale':
            for order in self:
                to_log = {}
                for order_line in order.order_line:
                    if float_compare(order_line.product_uom_qty,
                                     pre_order_line_qty.get(order_line, 0.0),
                                     order_line.product_uom.rounding) < 0:
                        to_log[order_line] = (order_line.product_uom_qty,
                                              pre_order_line_qty.get(
                                                  order_line, 0.0))
                if to_log:
                    documents = self.env[
                        'stock.picking']._log_activity_get_documents(
                            to_log, 'move_ids', 'UP')
                    documents = {
                        k: v
                        for k, v in documents.items() if k[0].state != 'cancel'
                    }
                    order._log_decrease_ordered_quantity(documents)
        return res
示例#27
0
 def reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False):
     res = super(AccountMoveLine,
                 self).reconcile(writeoff_acc_id=writeoff_acc_id,
                                 writeoff_journal_id=writeoff_journal_id)
     account_move_ids = [
         l.move_id.id for l in self
         if float_compare(l.move_id._get_cash_basis_matched_percentage(),
                          1,
                          precision_digits=5) == 0
     ]
     if account_move_ids:
         expense_sheets = self.env['hr.expense.sheet'].search([
             ('account_move_id', 'in', account_move_ids),
             ('state', '!=', 'done')
         ])
         expense_sheets.set_to_paid()
     return res
示例#28
0
 def test_channel_statistics(self):
     channel_publisher = self.channel.with_user(self.user_publisher)
     # slide type computation
     self.assertEqual(channel_publisher.total_slides, len(channel_publisher.slide_content_ids))
     self.assertEqual(channel_publisher.nbr_infographic, len(channel_publisher.slide_content_ids.filtered(lambda s: s.slide_type == 'infographic')))
     self.assertEqual(channel_publisher.nbr_presentation, len(channel_publisher.slide_content_ids.filtered(lambda s: s.slide_type == 'presentation')))
     self.assertEqual(channel_publisher.nbr_document, len(channel_publisher.slide_content_ids.filtered(lambda s: s.slide_type == 'document')))
     self.assertEqual(channel_publisher.nbr_video, len(channel_publisher.slide_content_ids.filtered(lambda s: s.slide_type == 'video')))
     # slide statistics computation
     self.assertEqual(float_compare(channel_publisher.total_time, sum(s.completion_time for s in channel_publisher.slide_content_ids), 3), 0)
     # members computation
     self.assertEqual(channel_publisher.members_count, 1)
     channel_publisher.action_add_member()
     self.assertEqual(channel_publisher.members_count, 1)
     channel_publisher._action_add_members(self.user_emp.partner_id)
     channel_publisher.invalidate_cache(['partner_ids'])
     self.assertEqual(channel_publisher.members_count, 2)
     self.assertEqual(channel_publisher.partner_ids, self.user_publisher.partner_id | self.user_emp.partner_id)
示例#29
0
 def write(self, values):
     lines = self.env['sale.order.line']
     if 'product_uom_qty' in values:
         precision = self.env['decimal.precision'].precision_get(
             'Product Unit of Measure')
         lines = self.filtered(
             lambda r: r.state == 'sale' and not r.is_expense and
             float_compare(r.product_uom_qty,
                           values['product_uom_qty'],
                           precision_digits=precision) == -1)
     previous_product_uom_qty = {
         line.id: line.product_uom_qty
         for line in lines
     }
     res = super(SaleOrderLine, self).write(values)
     if lines:
         lines._action_launch_stock_rule(previous_product_uom_qty)
     return res
示例#30
0
    def _run_pull(self, procurements):
        moves_values_by_company = defaultdict(list)
        mtso_products_by_locations = defaultdict(list)

        # To handle the `mts_else_mto` procure method, we do a preliminary loop to
        # isolate the products we would need to read the forecasted quantity,
        # in order to to batch the read. We also make a sanitary check on the
        # `location_src_id` field.
        for procurement, rule in procurements:
            if not rule.location_src_id:
                msg = _('No source location defined on stock rule: %s!') % (rule.name, )
                raise UserError(msg)

            if rule.procure_method == 'mts_else_mto':
                mtso_products_by_locations[rule.location_src_id].append(procurement.product_id.id)

        # Get the forecasted quantity for the `mts_else_mto` procurement.
        forecasted_qties_by_loc = {}
        for location, product_ids in mtso_products_by_locations.items():
            products = self.env['product.product'].browse(product_ids).with_context(location=location.id)
            forecasted_qties_by_loc[location] = {product.id: product.free_qty for product in products}

        # Prepare the move values, adapt the `procure_method` if needed.
        for procurement, rule in procurements:
            procure_method = rule.procure_method
            if rule.procure_method == 'mts_else_mto':
                qty_needed = procurement.product_uom._compute_quantity(procurement.product_qty, procurement.product_id.uom_id)
                qty_available = forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id]
                if float_compare(qty_needed, qty_available, precision_rounding=procurement.product_id.uom_id.rounding) <= 0:
                    procure_method = 'make_to_stock'
                    forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id] -= qty_needed
                else:
                    procure_method = 'make_to_order'

            move_values = rule._get_stock_move_values(*procurement)
            move_values['procure_method'] = procure_method
            moves_values_by_company[procurement.company_id.id].append(move_values)

        for company_id, moves_values in moves_values_by_company.items():
            # create the move as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
            moves = self.env['stock.move'].sudo().with_context(force_company=company_id).create(moves_values)
            # Since action_confirm launch following procurement_group we should activate it.
            moves._action_confirm()
        return True