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, })
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'}
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)
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, })
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')
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)
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)
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
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
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
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
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
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()
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 {}
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
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))
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
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)
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})
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
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
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, })
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
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
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()
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
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