Example #1
0
    def _compute_document_discount(self):
        for move in self:
            document_discount = 0
            discount_used = move.discount_type and not float_is_zero(move.discount_value, precision_digits=move.currency_id.decimal_places)
            if discount_used:
                if move.discount_type == 'fixed':
                    document_discount = move.discount_value * -1
                else:
                    document_discount = (move.amount_gross * (move.discount_value / 100)) * -1

            document_discount = float_round(document_discount, precision_digits=move.currency_id.decimal_places)

            discount_lines = move.line_ids.filtered(lambda f: f.is_document_discount_line)
            if discount_lines:
                document_discount_tax_amount = float_round(
                        sum(line.price_total - line.price_subtotal for line in discount_lines),
                        precision_digits=move.currency_id.decimal_places
                )
            else:
                document_discount_tax_amount = 0
            move.update({
                'document_discount': document_discount,
                'document_discount_tax_amount': document_discount_tax_amount,
                'has_document_discount': discount_used,
                'discount_value_percent': move.discount_value,
            })
Example #2
0
 def do_produce(self):
     # Nothing to do for lots since values are created using default data (stock.move.lots)
     quantity = self.product_qty
     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)
     for move in self.production_id.move_raw_ids:
         # TODO currently not possible to guess if the user updated quantity by hand or automatically by the produce wizard.
         if move.product_id.tracking == 'none' and move.state not in ('done', 'cancel') and move.unit_factor:
             rounding = move.product_uom.rounding
             if self.product_id.tracking != 'none':
                 qty_to_add = float_round(quantity * move.unit_factor, precision_rounding=rounding)
                 move._generate_consumed_move_line(qty_to_add, self.lot_id)
             elif len(move._get_move_lines()) < 2:
                 move.quantity_done += float_round(quantity * move.unit_factor, precision_rounding=rounding)
             else:
                 move._set_quantity_done(quantity * move.unit_factor)
     for move in self.production_id.move_finished_ids:
         if move.product_id.tracking == 'none' and move.state not in ('done', 'cancel'):
             rounding = move.product_uom.rounding
             if move.product_id.id == self.production_id.product_id.id:
                 move.quantity_done += float_round(quantity, precision_rounding=rounding)
             elif move.unit_factor:
                 # byproducts handling
                 move.quantity_done += float_round(quantity * move.unit_factor, precision_rounding=rounding)
     self.check_finished_move_lots()
     if self.production_id.state == 'confirmed':
         self.production_id.write({
             'state': 'progress',
             'date_start': datetime.now(),
         })
     return {'type': 'ir.actions.act_window_close'}
Example #3
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_compare(0.01, 0.02, precision_digits=3, precision_rounding=0.01)

        with self.assertRaises(AssertionError):
            float_round(0.01, precision_digits=3, precision_rounding=0.01)
Example #4
0
    def _run_valuation(self, quantity=None):
        self.ensure_one()
        if self._is_in():
            valued_move_lines = self.move_line_ids.filtered(lambda ml: not ml.location_id._should_be_valued() and ml.location_dest_id._should_be_valued() and not ml.owner_id)
            valued_quantity = 0
            for valued_move_line in valued_move_lines:
                valued_quantity += valued_move_line.product_uom_id._compute_quantity(valued_move_line.qty_done, self.product_id.uom_id)

            # Note: we always compute the fifo `remaining_value` and `remaining_qty` fields no
            # matter which cost method is set, to ease the switching of cost method.
            vals = {}
            price_unit = self._get_price_unit()
            value = price_unit * (quantity or valued_quantity)
            vals = {
                'price_unit': price_unit,
                'value': value if quantity is None or not self.value else self.value,
                'remaining_value': value if quantity is None else self.remaining_value + value,
            }
            vals['remaining_qty'] = valued_quantity if quantity is None else self.remaining_qty + quantity

            if self.product_id.cost_method == 'standard':
                value = self.product_id.standard_price * (quantity or valued_quantity)
                vals.update({
                    'price_unit': self.product_id.standard_price,
                    'value': value if quantity is None or not self.value else self.value,
                })
            self.write(vals)
        elif self._is_out():
            valued_move_lines = self.move_line_ids.filtered(lambda ml: ml.location_id._should_be_valued() and not ml.location_dest_id._should_be_valued() and not ml.owner_id)
            valued_quantity = 0
            for valued_move_line in valued_move_lines:
                valued_quantity += valued_move_line.product_uom_id._compute_quantity(valued_move_line.qty_done, self.product_id.uom_id)
            self.env['stock.move']._run_fifo(self, quantity=quantity)
            if self.product_id.cost_method in ['standard', 'average']:
                curr_rounding = self.company_id.currency_id.rounding
                value = -float_round(self.product_id.standard_price * (valued_quantity if quantity is None else quantity), precision_rounding=curr_rounding)
                self.write({
                    'value': value if quantity is None else self.value + value,
                    'price_unit': value / valued_quantity,
                })
        elif self._is_dropshipped():
            curr_rounding = self.company_id.currency_id.rounding
            if self.product_id.cost_method in ['fifo']:
                price_unit = self._get_price_unit()
                # see test_dropship_fifo_perpetual_anglosaxon_ordered
                self.product_id.standard_price = price_unit
            else:
                price_unit = self.product_id.standard_price
            value = float_round(self.product_qty * price_unit, precision_rounding=curr_rounding)
            # In move have a positive value, out move have a negative value, let's arbitrary say
            # dropship are positive.
            self.write({
                'value': value,
                'price_unit': price_unit,
            })
Example #5
0
    def test_13_negative_on_hand_qty(self):
        # We set the Product Unit of Measure digits to 5.
        # Because float_round(-384.0, 5) = -384.00000000000006
        # And float_round(-384.0, 2) = -384.0
        precision = self.env.ref('product.decimal_product_uom')
        precision.digits = 5

        # We set the Unit(s) rounding to 0.0001 (digit = 4)
        uom_unit = self.env.ref('uom.product_uom_unit')
        uom_unit.rounding = 0.0001

        _ = self.env['mrp.bom'].create({
            'product_id':
            self.product_2.id,
            'product_tmpl_id':
            self.product_2.product_tmpl_id.id,
            'product_uom_id':
            uom_unit.id,
            'product_qty':
            1.00,
            'type':
            'phantom',
            'bom_line_ids': [
                (0, 0, {
                    'product_id': self.product_3.id,
                    'product_qty': 1.000,
                }),
            ]
        })

        self.env['stock.quant']._update_available_quantity(
            self.product_3, self.env.ref('stock.stock_location_stock'), -384.0)

        kit_product_qty = self.product_2.qty_available  # Without product_3 in the prefetch
        # Use the float_repr to remove extra small decimal (and represent the front-end behavior)
        self.assertEqual(
            float_repr(float_round(kit_product_qty,
                                   precision_digits=precision.digits),
                       precision_digits=precision.digits), '-384.00000')

        self.product_2.invalidate_cache(fnames=['qty_available'],
                                        ids=self.product_2.ids)
        kit_product_qty, _ = (self.product_2 + self.product_3).mapped(
            "qty_available")  # With product_3 in the prefetch
        self.assertEqual(
            float_repr(float_round(kit_product_qty,
                                   precision_digits=precision.digits),
                       precision_digits=precision.digits), '-384.00000')
Example #6
0
 def _compute_should_consume_qty(self):
     for move in self:
         mo = move.raw_material_production_id
         if not mo or not move.product_uom:
             move.should_consume_qty = 0
             continue
         move.should_consume_qty = float_round((mo.qty_producing - mo.qty_produced) * move.unit_factor, precision_rounding=move.product_uom.rounding)
Example #7
0
    def round(self, amount):
        """Compute the rounding on the amount passed as parameter.

        :param amount: the amount to round
        :return: the rounded amount depending the rounding value and the rounding method
        """
        return float_round(amount, precision_rounding=self.rounding, rounding_method=self.rounding_method)
Example #8
0
 def _get_price(self, pricelist, product, qty):
     sale_price_digits = self.env['decimal.precision'].precision_get(
         'Product Price')
     price = pricelist.get_product_price(product, qty, False)
     if not price:
         price = product.list_price
     return float_round(price, precision_digits=sale_price_digits)
 def _get_operation_line(self, bom, qty, level):
     operations = []
     total = 0.0
     qty = bom.product_uom_id._compute_quantity(qty,
                                                bom.product_tmpl_id.uom_id)
     for operation in bom.operation_ids:
         operation_cycle = float_round(qty /
                                       operation.workcenter_id.capacity,
                                       precision_rounding=1,
                                       rounding_method='UP')
         duration_expected = operation_cycle * (
             operation.time_cycle + (operation.workcenter_id.time_stop +
                                     operation.workcenter_id.time_start))
         total = ((duration_expected / 60.0) *
                  operation.workcenter_id.costs_hour)
         operations.append({
             'level':
             level or 0,
             'operation':
             operation,
             'name':
             operation.name + ' - ' + operation.workcenter_id.name,
             'duration_expected':
             duration_expected,
             'total':
             self.env.company.currency_id.round(total),
         })
     return operations
Example #10
0
 def _compute_quantity(self,
                       qty,
                       to_unit,
                       round=True,
                       rounding_method='UP',
                       raise_if_failure=True):
     """ Convert the given quantity from the current UoM `self` into a given one
         :param qty: the quantity to convert
         :param to_unit: the destination UoM record (uom.uom)
         :param raise_if_failure: only if the conversion is not possible
             - if true, raise an exception if the conversion is not possible (different UoM category),
             - otherwise, return the initial quantity
     """
     if not self:
         return qty
     self.ensure_one()
     if self.category_id.id != to_unit.category_id.id:
         if raise_if_failure:
             raise UserError(
                 _('The unit of measure %s defined on the order line doesn\'t belong to the same category as the unit of measure %s defined on the product. Please correct the unit of measure defined on the order line or on the product, they should belong to the same category.'
                   ) % (self.name, to_unit.name))
         else:
             return qty
     amount = qty / self.factor
     if to_unit:
         amount = amount * to_unit.factor
         if round:
             amount = tools.float_round(amount,
                                        precision_rounding=to_unit.rounding,
                                        rounding_method=rounding_method)
     return amount
Example #11
0
    def create(self, values):
        if values.get('partner_id'):  # @TDENOTE: not sure
            values.update(
                self.on_change_partner_id(values['partner_id'])['value'])

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

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

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

        # Default value of reference is
        tx = super(PaymentTransaction, self).create(values)
        if not values.get('reference'):
            tx.write({'reference': str(tx.id)})

        # Generate callback hash if it is configured on the tx; avoid generating unnecessary stuff
        # (limited sudo env for checking callback presence, must work for manual transactions too)
        tx_sudo = tx.sudo()
        if tx_sudo.callback_model_id and tx_sudo.callback_res_id and tx_sudo.callback_method:
            tx.write({'callback_hash': tx._generate_callback_hash()})

        return tx
Example #12
0
 def _get_duration_expected(self, alternative_workcenter=False, ratio=1):
     self.ensure_one()
     if not self.workcenter_id:
         return self.duration_expected
     if not self.operation_id:
         duration_expected_working = (
             self.duration_expected - self.workcenter_id.time_start -
             self.workcenter_id.time_stop
         ) * self.workcenter_id.time_efficiency / 100.0
         if duration_expected_working < 0:
             duration_expected_working = 0
         return self.workcenter_id.time_start + self.workcenter_id.time_stop + duration_expected_working * ratio * 100.0 / self.workcenter_id.time_efficiency
     qty_production = self.production_id.product_uom_id._compute_quantity(
         self.qty_production, self.production_id.product_id.uom_id)
     cycle_number = float_round(qty_production /
                                self.workcenter_id.capacity,
                                precision_digits=0,
                                rounding_method='UP')
     if alternative_workcenter:
         # TODO : find a better alternative : the settings of workcenter can change
         duration_expected_working = (
             self.duration_expected - self.workcenter_id.time_start -
             self.workcenter_id.time_stop
         ) * self.workcenter_id.time_efficiency / (100.0 * cycle_number)
         if duration_expected_working < 0:
             duration_expected_working = 0
         return alternative_workcenter.time_start + alternative_workcenter.time_stop + cycle_number * duration_expected_working * 100.0 / alternative_workcenter.time_efficiency
     time_cycle = self.operation_id.time_cycle
     return self.workcenter_id.time_start + self.workcenter_id.time_stop + cycle_number * time_cycle * 100.0 / self.workcenter_id.time_efficiency
Example #13
0
    def action_pos_order_paid(self):
        self.ensure_one()

        # TODO: add support for mix of cash and non-cash payments when both cash_rounding and only_round_cash_method are True
        if not self.config_id.cash_rounding \
           or self.config_id.only_round_cash_method \
           and not any(p.payment_method_id.is_cash_count for p in self.payment_ids):
            total = self.amount_total
        else:
            total = float_round(self.amount_total, precision_rounding=self.config_id.rounding_method.rounding, rounding_method=self.config_id.rounding_method.rounding_method)

        isPaid = float_is_zero(total - self.amount_paid, precision_rounding=self.currency_id.rounding)

        if not isPaid and not self.config_id.cash_rounding:
            raise UserError(_("Order %s is not fully paid.", self.name))
        elif not isPaid and self.config_id.cash_rounding:
            currency = self.currency_id
            if self.config_id.rounding_method.rounding_method == "HALF-UP":
                maxDiff = currency.round(self.config_id.rounding_method.rounding / 2)
            else:
                maxDiff = currency.round(self.config_id.rounding_method.rounding)

            diff = currency.round(self.amount_total - self.amount_paid)
            if not abs(diff) <= maxDiff:
                raise UserError(_("Order %s is not fully paid.", self.name))

        self.write({'state': 'paid'})

        return True
    def _get_price(self, bom, factor, product):
        price = 0
        if bom.operation_ids:
            # routing are defined on a BoM and don't have a concept of quantity.
            # It means that the operation time are defined for the quantity on
            # the BoM (the user produces a batch of products). E.g the user
            # product a batch of 10 units with a 5 minutes operation, the time
            # will be the 5 for a quantity between 1-10, then doubled for
            # 11-20,...
            operation_cycle = float_round(factor,
                                          precision_rounding=1,
                                          rounding_method='UP')
            operations = self._get_operation_line(bom, operation_cycle, 0)
            price += sum([op['total'] for op in operations])

        for line in bom.bom_line_ids:
            if line._skip_bom_line(product):
                continue
            if line.child_bom_id:
                qty = line.product_uom_id._compute_quantity(
                    line.product_qty *
                    (factor / bom.product_qty), line.child_bom_id.
                    product_uom_id) / line.child_bom_id.product_qty
                sub_price = self._get_price(line.child_bom_id, qty,
                                            line.product_id)
                price += sub_price
            else:
                prod_qty = line.product_qty * factor / bom.product_qty
                company = bom.company_id or self.env.company
                not_rounded_price = line.product_id.uom_id._compute_price(
                    line.product_id.with_context(
                        force_comany=company.id).standard_price,
                    line.product_uom_id) * prod_qty
                price += company.currency_id.round(not_rounded_price)
        return price
Example #15
0
 def check_finished_move_lots(self):
     """ Handle by product tracked """
     by_product_moves = self.production_id.move_finished_ids.filtered(
         lambda m: m.product_id != self.product_id and m.product_id.tracking
         != 'none' and m.state not in ('done', 'cancel'))
     for by_product_move in by_product_moves:
         rounding = by_product_move.product_uom.rounding
         quantity = float_round(self.product_qty *
                                by_product_move.unit_factor,
                                precision_rounding=rounding)
         values = {
             'move_id': by_product_move.id,
             'product_id': by_product_move.product_id.id,
             'production_id': self.production_id.id,
             'product_uom_id': by_product_move.product_uom.id,
             'location_id': by_product_move.location_id.id,
             'location_dest_id': by_product_move.location_dest_id.id,
         }
         if by_product_move.product_id.tracking == 'lot':
             values.update({
                 'product_uom_qty': quantity,
                 'qty_done': quantity,
             })
             self.env['stock.move.line'].create(values)
         else:
             values.update({
                 'product_uom_qty': 1.0,
                 'qty_done': 1.0,
             })
             for i in range(0, int(quantity)):
                 self.env['stock.move.line'].create(values)
     return super(MrpProductProduce, self).check_finished_move_lots()
Example #16
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 %(pack_size).2f %(pack_name)s. You should sell %(quantity).2f %(unit)s.",
                   pack_size=pack.qty,
                   pack_name=default_uom.name,
                   quantity=newqty,
                   unit=self.product_uom.name),
             },
         }
     return {}
Example #17
0
 def _onchange_qty_producing(self):
     """ Update stock.move.lot records, according to the new qty currently
     produced. """
     moves = self.move_raw_ids.filtered(lambda move: move.state not in ('done', 'cancel') and move.product_id.tracking != 'none' and move.product_id.id != self.production_id.product_id.id)
     for move in moves:
         move_lots = self.active_move_line_ids.filtered(lambda move_lot: move_lot.move_id == move)
         if not move_lots:
             continue
         rounding = move.product_uom.rounding
         new_qty = float_round(move.unit_factor * self.qty_producing, precision_rounding=rounding)
         if move.product_id.tracking == 'lot':
             move_lots[0].product_qty = new_qty
             move_lots[0].qty_done = new_qty
         elif move.product_id.tracking == 'serial':
             # Create extra pseudo record
             qty_todo = float_round(new_qty - sum(move_lots.mapped('qty_done')), precision_rounding=rounding)
             if float_compare(qty_todo, 0.0, precision_rounding=rounding) > 0:
                 while float_compare(qty_todo, 0.0, precision_rounding=rounding) > 0:
                     self.active_move_line_ids += self.env['stock.move.line'].new({
                         'move_id': move.id,
                         'product_id': move.product_id.id,
                         'lot_id': False,
                         'product_uom_qty': 0.0,
                         'product_uom_id': move.product_uom.id,
                         'qty_done': min(1.0, qty_todo),
                         'workorder_id': self.id,
                         'done_wo': False,
                         'location_id': move.location_id.id,
                         'location_dest_id': move.location_dest_id.id,
                         'date': move.date,
                     })
                     qty_todo -= 1
             elif float_compare(qty_todo, 0.0, precision_rounding=rounding) < 0:
                 qty_todo = abs(qty_todo)
                 for move_lot in move_lots:
                     if float_compare(qty_todo, 0, precision_rounding=rounding) <= 0:
                         break
                     if not move_lot.lot_id and float_compare(qty_todo, move_lot.qty_done, precision_rounding=rounding) >= 0:
                         qty_todo = float_round(qty_todo - move_lot.qty_done, precision_rounding=rounding)
                         self.active_move_line_ids -= move_lot  # Difference operator
                     else:
                         #move_lot.product_qty = move_lot.product_qty - qty_todo
                         if float_compare(move_lot.qty_done - qty_todo, 0, precision_rounding=rounding) == 1:
                             move_lot.qty_done = move_lot.qty_done - qty_todo
                         else:
                             move_lot.qty_done = 0
                         qty_todo = 0
Example #18
0
 def try_round(amount, expected, digits=3, method='HALF-UP'):
     value = float_round(amount,
                         precision_digits=digits,
                         rounding_method=method)
     result = float_repr(value, precision_digits=digits)
     self.assertEqual(
         result, expected,
         'Rounding error: got %s, expected %s' % (result, expected))
Example #19
0
 def _adyen_convert_amount(self, amount, currency):
     """
     Adyen requires the amount to be multiplied by 10^k,
     where k depends on the currency code.
     """
     k = CURRENCY_CODE_MAPS.get(currency.name, 2)
     paymentAmount = int(tools.float_round(amount, k) * (10**k))
     return paymentAmount
Example #20
0
    def round(self, amount):
        """Return ``amount`` rounded  according to ``self``'s rounding rules.

           :param float amount: the amount to round
           :return: rounded float
        """
        self.ensure_one()
        return tools.float_round(amount, precision_rounding=self.rounding)
Example #21
0
def float_to_time(hours, moment='am'):
    """ Convert a number of hours into a time object. """
    if hours == 12.0 and moment == 'pm':
        return time.max
    fractional, integral = math.modf(hours)
    if moment == 'pm':
        integral += 12
    return time(int(integral),
                int(float_round(60 * fractional, precision_digits=0)), 0)
 def get_operations(self, bom_id=False, qty=0, level=0):
     bom = self.env['mrp.bom'].browse(bom_id)
     lines = self._get_operation_line(bom, float_round(qty / bom.product_qty, precision_rounding=1, rounding_method='UP'), level)
     values = {
         'bom_id': bom_id,
         'currency': self.env.company.currency_id,
         'operations': lines,
     }
     return self.env.ref('mrp.report_mrp_operation_line')._render({'data': values})
Example #23
0
 def get_wallet_balance(self, user, include_config=True):
     result = float_round(sum(
         move['amount']
         for move in self.env['lunch.cashmove.report'].search_read([(
             'user_id', '=', user.id)], ['amount'])),
                          precision_digits=2)
     if include_config:
         result += user.company_id.lunch_minimum_threshold
     return result
Example #24
0
    def explode(self, product, quantity, picking_type=False):
        """
            Explodes the BoM and creates two lists with all the information you need: bom_done and line_done
            Quantity describes the number of times you need the BoM: so the quantity divided by the number created by the BoM
            and converted into its UoM
        """
        from collections import defaultdict

        graph = defaultdict(list)
        V = set()

        def check_cycle(v, visited, recStack, graph):
            visited[v] = True
            recStack[v] = True
            for neighbour in graph[v]:
                if visited[neighbour] == False:
                    if check_cycle(neighbour, visited, recStack, graph) == True:
                        return True
                elif recStack[neighbour] == True:
                    return True
            recStack[v] = False
            return False

        boms_done = [(self, {'qty': quantity, 'product': product, 'original_qty': quantity, 'parent_line': False})]
        lines_done = []
        V |= set([product.product_tmpl_id.id])

        bom_lines = [(bom_line, product, quantity, False) for bom_line in self.bom_line_ids]
        for bom_line in self.bom_line_ids:
            V |= set([bom_line.product_id.product_tmpl_id.id])
            graph[product.product_tmpl_id.id].append(bom_line.product_id.product_tmpl_id.id)
        while bom_lines:
            current_line, current_product, current_qty, parent_line = bom_lines[0]
            bom_lines = bom_lines[1:]

            if current_line._skip_bom_line(current_product):
                continue

            line_quantity = current_qty * current_line.product_qty
            bom = self._bom_find(product=current_line.product_id, picking_type=picking_type or self.picking_type_id, company_id=self.company_id.id)
            if bom.type == 'phantom':
                converted_line_quantity = current_line.product_uom_id._compute_quantity(line_quantity / bom.product_qty, bom.product_uom_id)
                bom_lines = [(line, current_line.product_id, converted_line_quantity, current_line) for line in bom.bom_line_ids] + bom_lines
                for bom_line in bom.bom_line_ids:
                    graph[current_line.product_id.product_tmpl_id.id].append(bom_line.product_id.product_tmpl_id.id)
                    if bom_line.product_id.product_tmpl_id.id in V and check_cycle(bom_line.product_id.product_tmpl_id.id, {key: False for  key in V}, {key: False for  key in V}, graph):
                        raise UserError(_('Recursion error!  A product with a Bill of Material should not have itself in its BoM or child BoMs!'))
                    V |= set([bom_line.product_id.product_tmpl_id.id])
                boms_done.append((bom, {'qty': converted_line_quantity, 'product': current_product, 'original_qty': quantity, 'parent_line': current_line}))
            else:
                # We round up here because the user expects that if he has to consume a little more, the whole UOM unit
                # should be consumed.
                rounding = current_line.product_uom_id.rounding
                line_quantity = float_round(line_quantity, precision_rounding=rounding, rounding_method='UP')
                lines_done.append((current_line, {'qty': line_quantity, 'product': current_product, 'original_qty': quantity, 'parent_line': parent_line}))

        return boms_done, lines_done
Example #25
0
    def _run_valuation(self, quantity=None):
        self.ensure_one()
        if self._is_in():
            valued_move_lines = self.move_line_ids.filtered(
                lambda ml: not ml.location_id._should_be_valued() and ml.
                location_dest_id._should_be_valued() and not ml.owner_id)
            valued_quantity = 0
            for valued_move_line in valued_move_lines:
                valued_quantity += valued_move_line.product_uom_id._compute_quantity(
                    valued_move_line.qty_done, self.product_id.uom_id)

            # Note: we always compute the fifo `remaining_value` and `remaining_qty` fields no
            # matter which cost method is set, to ease the switching of cost method.
            vals = {}
            price_unit = self._get_price_unit()
            value = price_unit * (quantity or valued_quantity)
            vals = {
                'price_unit':
                price_unit,
                'value':
                value if quantity is None or not self.value else self.value,
                'remaining_value':
                value if quantity is None else self.remaining_value + value,
            }
            vals[
                'remaining_qty'] = valued_quantity if quantity is None else self.remaining_qty + quantity

            if self.product_id.cost_method == 'standard':
                value = self.product_id.standard_price * (quantity
                                                          or valued_quantity)
                vals.update({
                    'price_unit':
                    self.product_id.standard_price,
                    'value':
                    value
                    if quantity is None or not self.value else self.value,
                })
            self.write(vals)
        elif self._is_out():
            valued_move_lines = self.move_line_ids.filtered(
                lambda ml: ml.location_id._should_be_valued() and not ml.
                location_dest_id._should_be_valued() and not ml.owner_id)
            valued_quantity = sum(valued_move_lines.mapped('qty_done'))
            self.env['stock.move']._run_fifo(self, quantity=quantity)
            if self.product_id.cost_method in ['standard', 'average']:
                curr_rounding = self.company_id.currency_id.rounding
                value = -float_round(
                    self.product_id.standard_price *
                    (valued_quantity if quantity is None else quantity),
                    precision_rounding=curr_rounding)
                self.write({
                    'value':
                    value if quantity is None else self.value + value,
                    'price_unit':
                    value / valued_quantity,
                })
Example #26
0
    def infos(self, user_id=None):
        self._check_user_impersonification(user_id)
        user = request.env['res.users'].browse(
            user_id) if user_id else request.env.user

        infos = self._make_infos(user, order=False)

        lines = self._get_current_lines(user.id)
        if lines:
            lines = [
                {
                    'id':
                    line.id,
                    'product': (line.product_id.id, line.product_id.name,
                                float_repr(float_round(line.price, 2), 2)),
                    'toppings':
                    [(topping.name, float_repr(float_round(topping.price, 2),
                                               2))
                     for topping in line.topping_ids_1 | line.topping_ids_2
                     | line.topping_ids_3],
                    'quantity':
                    line.quantity,
                    'price':
                    line.price,
                    'state':
                    line.state,  # Only used for _get_state
                    'note':
                    line.note
                } for line in lines
            ]
            raw_state, state = self._get_state(lines)
            infos.update({
                'total':
                float_repr(
                    float_round(sum(line['price'] for line in lines), 2), 2),
                'raw_state':
                raw_state,
                'state':
                state,
                'lines':
                lines,
            })
        return infos
Example #27
0
    def adyen_form_generate_values(self, values):
        base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
        # tmp
        import datetime
        from dateutil import relativedelta

        if self.provider == 'adyen' and len(self.adyen_skin_hmac_key) == 64:
            tmp_date = datetime.datetime.today() + relativedelta.relativedelta(days=1)

            values.update({
                'merchantReference': values['reference'],
                'paymentAmount': '%d' % int(tools.float_round(values['amount'], 2) * 100),
                'currencyCode': values['currency'] and values['currency'].name or '',
                'shipBeforeDate': tmp_date.strftime('%Y-%m-%d'),
                'skinCode': self.adyen_skin_code,
                'merchantAccount': self.adyen_merchant_account,
                'shopperLocale': values.get('partner_lang', ''),
                'sessionValidity': tmp_date.isoformat('T')[:19] + "Z",
                'resURL': urls.url_join(base_url, AdyenController._return_url),
                'merchantReturnData': json.dumps({'return_url': '%s' % values.pop('return_url')}) if values.get('return_url', '') else False,
                'shopperEmail': values.get('partner_email', ''),
            })
            values['merchantSig'] = self._adyen_generate_merchant_sig_sha256('in', values)

        else:
            tmp_date = datetime.date.today() + relativedelta.relativedelta(days=1)

            values.update({
                'merchantReference': values['reference'],
                'paymentAmount': '%d' % int(tools.float_round(values['amount'], 2) * 100),
                'currencyCode': values['currency'] and values['currency'].name or '',
                'shipBeforeDate': tmp_date,
                'skinCode': self.adyen_skin_code,
                'merchantAccount': self.adyen_merchant_account,
                'shopperLocale': values.get('partner_lang'),
                'sessionValidity': tmp_date,
                'resURL': urls.url_join(base_url, AdyenController._return_url),
                'merchantReturnData': json.dumps({'return_url': '%s' % values.pop('return_url')}) if values.get('return_url') else False,
            })
            values['merchantSig'] = self._adyen_generate_merchant_sig('in', values)

        return values
Example #28
0
 def test_paid(self):
     if self.config_id.cash_rounding:
         total = float_round(
             self.amount_total,
             precision_rounding=self.config_id.rounding_method.rounding,
             rounding_method=self.config_id.rounding_method.rounding_method)
         return float_is_zero(
             total - self.amount_paid,
             precision_rounding=self.config_id.currency_id.rounding)
     else:
         return super(PosOrder, self).test_paid()
Example #29
0
 def _compute_kit_quantities(self, product_id, kit_qty, kit_bom, filters):
     """ Computes the quantity delivered or received when a kit is sold or purchased.
     A ratio 'qty_processed/qty_needed' is computed for each component, and the lowest one is kept
     to define the kit's quantity delivered or received.
     :param product_id: The kit itself a.k.a. the finished product
     :param kit_qty: The quantity from the order line
     :param kit_bom: The kit's BoM
     :param filters: Dict of lambda expression to define the moves to consider and the ones to ignore
     :return: The quantity delivered or received
     """
     qty_ratios = []
     boms, bom_sub_lines = kit_bom.explode(product_id, kit_qty)
     for bom_line, bom_line_data in bom_sub_lines:
         # skip service since we never deliver them
         if bom_line.product_id.type == 'service':
             continue
         if float_is_zero(
                 bom_line_data['qty'],
                 precision_rounding=bom_line.product_uom_id.rounding):
             # As BoMs allow components with 0 qty, a.k.a. optionnal components, we simply skip those
             # to avoid a division by zero.
             continue
         bom_line_moves = self.filtered(lambda m: m.bom_line_id == bom_line)
         if bom_line_moves:
             # We compute the quantities needed of each components to make one kit.
             # Then, we collect every relevant moves related to a specific component
             # to know how many are considered delivered.
             uom_qty_per_kit = bom_line_data['qty'] / bom_line_data[
                 'original_qty']
             qty_per_kit = bom_line.product_uom_id._compute_quantity(
                 uom_qty_per_kit, bom_line.product_id.uom_id, round=False)
             if not qty_per_kit:
                 continue
             incoming_moves = bom_line_moves.filtered(
                 filters['incoming_moves'])
             outgoing_moves = bom_line_moves.filtered(
                 filters['outgoing_moves'])
             qty_processed = sum(
                 incoming_moves.mapped('product_qty')) - sum(
                     outgoing_moves.mapped('product_qty'))
             # We compute a ratio to know how many kits we can produce with this quantity of that specific component
             qty_ratios.append(
                 float_round(qty_processed / qty_per_kit,
                             precision_rounding=bom_line.product_id.uom_id.
                             rounding))
         else:
             return 0.0
     if qty_ratios:
         # Now that we have every ratio by components, we keep the lowest one to know how many kits we can produce
         # with the quantities delivered of each component. We use the floor division here because a 'partial kit'
         # doesn't make sense.
         return min(qty_ratios) // 1
     else:
         return 0.0
Example #30
0
 def _run_valuation(self, quantity=None):
     # Extend `_run_valuation` to make it work on internal moves.
     self.ensure_one()
     res = super()._run_valuation(quantity)
     if self._is_internal() and not self.value:
         # TODO: recheck if this part respects product valuation method
         self.value = float_round(
             value=self.product_id.standard_price * self.quantity_done,
             precision_rounding=self.company_id.currency_id.rounding,
         )
     return res