def do_produce(self): # Nothing to do for lots since values are created using default data (stock.move.lots) moves = self.production_id.move_raw_ids quantity = self.product_qty if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0: raise UserError(_('You should at least produce some quantity')) for move in moves.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel')): if move.unit_factor: rounding = move.product_uom.rounding move.quantity_done_store += float_round(quantity * move.unit_factor, precision_rounding=rounding) moves = self.production_id.move_finished_ids.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel')) for move in moves: rounding = move.product_uom.rounding if move.product_id.id == self.production_id.product_id.id: move.quantity_done_store += float_round(quantity, precision_rounding=rounding) elif move.unit_factor: # byproducts handling move.quantity_done_store += 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 _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) value_to_return = value if quantity is None or not self.value else self.value vals = { 'price_unit': price_unit, 'value': value_to_return, '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) value_to_return = value if quantity is None or not self.value else self.value vals.update({ 'price_unit': self.product_id.standard_price, 'value': value_to_return, }) 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) value_to_return = value if quantity is None else self.value + value self.write({ 'value': value_to_return, 'price_unit': value / valued_quantity, }) elif self._is_dropshipped() or self._is_dropshipped_returned(): 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) value_to_return = value if self._is_dropshipped() else -value # In move have a positive value, out move have a negative value, let's arbitrary say # dropship are positive. self.write({ 'value': value_to_return, 'price_unit': price_unit if self._is_dropshipped() else -price_unit, }) return value_to_return
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(_('You should at least produce some quantity')) 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.quantity_done < move.product_uom_qty and move.state not in ('done', 'cancel') and move.unit_factor: rounding = move.product_uom.rounding move.quantity_done += float_round(quantity * move.unit_factor, precision_rounding=rounding) 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 do_produce(self): # apply work time to work orders self.update_work_time() # if work time was successfully applied to the work orders: # complete the work orders self.complete_workorders() moves = self.production_id.move_raw_ids quantity = self.product_qty if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0: raise UserError(_('You should at least produce some quantity')) for move in moves.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel')): if move.unit_factor: rounding = move.product_uom.rounding move.quantity_done_store += float_round(quantity * move.unit_factor, precision_rounding=rounding) moves = self.production_id.move_finished_ids.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel')) for move in moves: rounding = move.product_uom.rounding if move.product_id.id == self.production_id.product_id.id: move.quantity_done_store += float_round(quantity, precision_rounding=rounding) elif move.unit_factor: # byproducts handling move.quantity_done_store += 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': fields.datetime.now(), }) return {'type': 'ir.actions.act_window_close'}
def planned_qty(self, to_date=None): """Return planned net move quantity, for select products, through the given date""" # build domain for searching mrp.material_plan records domain = [('product_id', 'in', self.ids)] if to_date: domain += [('date_finish', '<', to_date)] domain_in = domain + [('move_type', '=', 'supply')] domain_out = domain + [('move_type', '=', 'demand')] # get moves MrpPlan = self.env['mrp.material_plan'] moves_in = dict((item['product_id'][0], item['product_qty']) for item in MrpPlan.read_group(domain_in, ['product_id', 'product_qty'], ['product_id'])) moves_out = dict((item['product_id'][0], item['product_qty']) for item in MrpPlan.read_group(domain_out, ['product_id', 'product_qty'], ['product_id'])) # return dict res = dict() for product in self.with_context(prefetch_fields=False): qty_in = float_round(moves_in.get(product.id, 0.0), precision_rounding=product.uom_id.rounding) qty_out = float_round(moves_out.get(product.id, 0.0), precision_rounding=product.uom_id.rounding) res[product.id] = { 'qty_in': qty_in, 'qty_out': qty_out, 'qty_net': float_round(qty_in - qty_out, precision_rounding=product.uom_id.rounding), } return res
def button_apply_work(self): self.ensure_one() detail_time = sum(self.detail_ids.mapped('minutes_assigned')) / 60 or 0.0 rounded_detail_time = float_round(detail_time, precision_digits=2) rounded_total_time = float_round(self.total_hours, precision_digits=2) if rounded_detail_time != rounded_total_time: raise UserError( "Time assigned on detail lines ({} hours) doesn't sum to the " "total time on the batch ({} hours)".format( rounded_detail_time, rounded_total_time)) # check for canceled or completed orders comp_lines = self.detail_ids.filtered( lambda x: x.production_state in ('done', 'cancel')) if comp_lines: message = "The following orders have already been completed (or " \ "canceled). You should reassign orders.\n" message += ", ".join(comp_lines.mapped('production_id.name')) raise UserError(message) for detail in self.detail_ids.filtered(lambda r: r.production_id): mo = detail.production_id _logger.info(mo.name) if not mo.product_id.id == detail.product_id.id: raise UserError( "{} does not match the product on {} ({})".format( detail.product_id.default_code, mo.name, mo.product_id.default_code)) if mo.state == 'confirmed': mo.button_plan() if detail.actual_quantity != mo.product_qty: change_wiz = self.env['change.production.qty'].create( {'mo_id': mo.id, 'product_qty': detail.actual_quantity}) change_wiz.change_prod_qty() ctx = dict(self.env.context) ctx['default_production_id'] = mo.id produce_wiz = self.env['mrp.wo.produce'].with_context(ctx).create({'production_id': mo.id}) # divide time evenly across workorders produce_wiz.load_work() wo_count = len(produce_wiz.work_ids) for work in produce_wiz.work_ids: if detail.minutes_assigned <= 1.0: raise UserError("Work time cannot be less than 1 minute per manufacturing order") labor_time = (detail.minutes_assigned/60)/wo_count work.update({ 'user_id': self.work_user_id.id, 'labor_date': self.work_date, 'labor_time': labor_time, }) produce_wiz.do_produce() mo.button_mark_done() self.state = 'closed'
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 adyen_form_generate_values(self, values): base_url = self.env["ir.config_parameter"].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": "%s" % urlparse.urljoin(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": "%s" % urlparse.urljoin(base_url, AdyenController._return_url), "merchantReturnData": json.dumps({"return_url": "%s" % values.pop("return_url")}) if values.get("return_url") else False, "merchantSig": self._adyen_generate_merchant_sig("in", values), } ) return values
def _get_operation_line(self, routing, qty, level): operations = [] total = 0.0 for operation in routing.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': float_round(total, precision_rounding=self.env.user.company_id.currency_id.rounding), }) return operations
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)}) return tx
def _quant_create_from_move(self, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location_from=False, force_location_to=False): quant = super(StockQuant, self)._quant_create_from_move(qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location_from=force_location_from, force_location_to=force_location_to) quant._account_entry_move(move) if move.product_id.valuation == 'real_time': # If the precision required for the variable quant cost is larger than the accounting # precision, inconsistencies between the stock valuation and the accounting entries # may arise. # For example, a box of 13 units is bought 15.00. If the products leave the # stock one unit at a time, the amount related to the cost will correspond to # round(15/13, 2)*13 = 14.95. To avoid this case, we split the quant in 12 + 1, then # record the difference on the new quant. # We need to make sure to able to extract at least one unit of the product. There is # an arbitrary minimum quantity set to 2.0 from which we consider we can extract a # unit and adapt the cost. curr_rounding = move.company_id.currency_id.rounding cost_rounded = float_round(quant.cost, precision_rounding=curr_rounding) cost_correct = cost_rounded if float_compare(quant.product_id.uom_id.rounding, 1.0, precision_digits=1) == 0\ and float_compare(quant.qty * quant.cost, quant.qty * cost_rounded, precision_rounding=curr_rounding) != 0\ and float_compare(quant.qty, 2.0, precision_rounding=quant.product_id.uom_id.rounding) >= 0: quant_correct = quant._quant_split(quant.qty - 1.0) cost_correct += (quant.qty * quant.cost) - (quant.qty * cost_rounded) quant.sudo().write({'cost': cost_rounded}) quant_correct.sudo().write({'cost': cost_correct}) return quant
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)}) return tx
def _update_finished_move(self): """ Update the finished move & move lines in order to set the finished product lot on it as well as the produced quantity. This method get the information either from the last workorder or from the Produce wizard.""" production_move = self.production_id.move_finished_ids.filtered( lambda move: move.product_id == self.product_id and move.state not in ('done', 'cancel') ) if production_move and production_move.product_id.tracking != 'none': if not self.final_lot_id: raise UserError(_('You need to provide a lot for the finished product.')) move_line = production_move.move_line_ids.filtered( lambda line: line.lot_id.id == self.final_lot_id.id ) if move_line: if self.product_id.tracking == 'serial': raise UserError(_('You cannot produce the same serial number twice.')) move_line.product_uom_qty += self.qty_producing move_line.qty_done += self.qty_producing else: move_line.create({ 'move_id': production_move.id, 'product_id': production_move.product_id.id, 'lot_id': self.final_lot_id.id, 'product_uom_qty': self.qty_producing, 'product_uom_id': self.product_uom_id.id, 'qty_done': self.qty_producing, 'location_id': production_move.location_id.id, 'location_dest_id': production_move.location_dest_id.id, }) else: rounding = production_move.product_uom.rounding production_move._set_quantity_done( production_move.quantity_done + float_round(self.qty_producing, precision_rounding=rounding) )
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_bom_lines(self, bom, bom_quantity, product, line_id, level): components = [] total = 0 for line in bom.bom_line_ids: line_quantity = (bom_quantity / (bom.product_qty or 1.0)) * line.product_qty if line._skip_bom_line(product): continue price = line.product_id.uom_id._compute_price(line.product_id.standard_price, line.product_uom_id) * line_quantity if line.child_bom_id: factor = float_round(line.product_uom_id._compute_quantity(line_quantity, line.child_bom_id.product_uom_id) / line.child_bom_id.product_qty, precision_rounding=1, rounding_method='UP') sub_total = self._get_price(line.child_bom_id, factor) else: sub_total = price components.append({ 'prod_id': line.product_id.id, 'prod_name': line.product_id.display_name, 'code': line.child_bom_id and self._get_bom_reference(line.child_bom_id) or '', 'prod_qty': line_quantity, 'prod_uom': line.product_uom_id.name, 'prod_cost': price, 'parent_id': bom.id, 'line_id': line.id, 'level': level or 0, 'total': sub_total, 'child_bom': line.child_bom_id.id, 'phantom_bom': line.child_bom_id and line.child_bom_id.type == 'phantom' or False, 'attachments': self.env['mrp.document'].search(['|', '&', ('res_model', '=', 'product.product'), ('res_id', '=', line.product_id.id), '&', ('res_model', '=', 'product.template'), ('res_id', '=', line.product_id.product_tmpl_id.id)]), }) total += sub_total return components, total
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 """ boms_done = [(self, {'qty': quantity, 'product': product, 'original_qty': quantity, 'parent_line': False})] lines_done = [] templates_done = product.product_tmpl_id bom_lines = [(bom_line, product, quantity, False) for bom_line in self.bom_line_ids] 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 if current_line.product_id.product_tmpl_id in templates_done: raise UserError(_('Recursion error! A product with a Bill of Material should not have itself in its BoM or child BoMs!')) 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 templates_done |= current_line.product_id.product_tmpl_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(): if self.product_id.cost_method in ['fifo', 'average']: price_unit = self._get_price_unit() value = price_unit * (quantity or self.product_qty) 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, } if self.product_id.cost_method == 'fifo': vals['remaining_qty'] = self.product_qty if quantity is None else self.remaining_qty + quantity self.write(vals) else: # standard value = self.product_id.standard_price * (quantity or self.product_qty) self.write({ 'price_unit': self.product_id.standard_price, 'value': value if quantity is None or not self.value else self.value, }) elif self._is_out(): if self.product_id.cost_method == 'fifo': self.env['stock.move']._run_fifo(self, quantity=quantity) elif self.product_id.cost_method in ['standard', 'average']: curr_rounding = self.company_id.currency_id.rounding value = -float_round(self.product_id.standard_price * (self.product_qty 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 / self.product_qty, })
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 _action_done(self): self.product_price_update_before_done() res = super(StockMove, self)._action_done() for move in res: if move._is_in(): if move.product_id.cost_method in ['fifo', 'average']: price_unit = move.price_unit or move._get_price_unit() value = price_unit * move.product_qty vals = { 'price_unit': price_unit, 'value': value, 'remaining_value': value, } if move.product_id.cost_method == 'fifo': vals['remaining_qty'] = move.product_qty move.write(vals) else: # standard move.write({ 'price_unit': move.product_id.standard_price, 'value': move.product_id.standard_price * move.product_qty, }) elif move._is_out(): if move.product_id.cost_method == 'fifo': self.env['stock.move']._run_fifo(move) elif move.product_id.cost_method in ['standard', 'average']: curr_rounding = move.company_id.currency_id.rounding value = -float_round(move.product_id.standard_price * move.product_qty, precision_rounding=curr_rounding) move.write({ 'value': value, 'price_unit': value / move.product_qty, }) for move in res.filtered(lambda m: m.product_id.valuation == 'real_time' and (m._is_in() or m._is_out())): move._account_entry_move() return res
def ogone_form_generate_values(self, values): base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') ogone_tx_values = dict(values) temp_ogone_tx_values = { 'PSPID': self.ogone_pspid, 'ORDERID': values['reference'], 'AMOUNT': float_repr(float_round(values['amount'], 2) * 100, 0), 'CURRENCY': values['currency'] and values['currency'].name or '', 'LANGUAGE': values.get('partner_lang'), 'CN': values.get('partner_name'), 'EMAIL': values.get('partner_email'), 'OWNERZIP': values.get('partner_zip'), 'OWNERADDRESS': values.get('partner_address'), 'OWNERTOWN': values.get('partner_city'), 'OWNERCTY': values.get('partner_country') and values.get('partner_country').code or '', 'OWNERTELNO': values.get('partner_phone'), 'ACCEPTURL': urls.url_join(base_url, OgoneController._accept_url), 'DECLINEURL': urls.url_join(base_url, OgoneController._decline_url), 'EXCEPTIONURL': urls.url_join(base_url, OgoneController._exception_url), 'CANCELURL': urls.url_join(base_url, OgoneController._cancel_url), 'PARAMPLUS': 'return_url=%s' % ogone_tx_values.pop('return_url') if ogone_tx_values.get('return_url') else False, } if self.save_token in ['ask', 'always']: temp_ogone_tx_values.update({ 'ALIAS': 'ODOO-NEW-ALIAS-%s' % time.time(), # something unique, 'ALIASUSAGE': values.get('alias_usage') or self.ogone_alias_usage, }) shasign = self._ogone_generate_shasign('in', temp_ogone_tx_values) temp_ogone_tx_values['SHASIGN'] = shasign ogone_tx_values.update(temp_ogone_tx_values) return ogone_tx_values
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, bom, factor, product): price = 0 if bom.routing_id: # 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.routing_id, 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, line.child_bom_id.product_uom_id) sub_price = self._get_price(line.child_bom_id, qty, line.product_id) price += sub_price else: prod_qty = line.product_qty * factor not_rounded_price = line.product_id.uom_id._compute_price(line.product_id.standard_price, line.product_uom_id) * prod_qty price += self.env.user.company_id.currency_id.round(not_rounded_price) return price
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 compute_landed_cost(self): AdjustementLines = self.env['stock.valuation.adjustment.lines'] AdjustementLines.search([('cost_id', 'in', self.ids)]).unlink() digits = dp.get_precision('Product Price')(self._cr) towrite_dict = {} for cost in self.filtered(lambda cost: cost.picking_ids): total_qty = 0.0 total_cost = 0.0 total_weight = 0.0 total_volume = 0.0 total_line = 0.0 all_val_line_values = cost.get_valuation_lines() for val_line_values in all_val_line_values: for cost_line in cost.cost_lines: val_line_values.update({'cost_id': cost.id, 'cost_line_id': cost_line.id}) self.env['stock.valuation.adjustment.lines'].create(val_line_values) total_qty += val_line_values.get('quantity', 0.0) total_cost += val_line_values.get('former_cost', 0.0) total_weight += val_line_values.get('weight', 0.0) total_volume += val_line_values.get('volume', 0.0) total_line += 1 for line in cost.cost_lines: value_split = 0.0 for valuation in cost.valuation_adjustment_lines: value = 0.0 if valuation.cost_line_id and valuation.cost_line_id.id == line.id: if line.split_method == 'by_quantity' and total_qty: per_unit = (line.price_unit / total_qty) value = valuation.quantity * per_unit elif line.split_method == 'by_weight' and total_weight: per_unit = (line.price_unit / total_weight) value = valuation.weight * per_unit elif line.split_method == 'by_volume' and total_volume: per_unit = (line.price_unit / total_volume) value = valuation.volume * per_unit elif line.split_method == 'equal': value = (line.price_unit / total_line) elif line.split_method == 'by_current_cost_price' and total_cost: per_unit = (line.price_unit / total_cost) value = valuation.former_cost * per_unit else: value = (line.price_unit / total_line) if digits: value = tools.float_round(value, precision_digits=digits[1], rounding_method='UP') fnc = min if line.price_unit > 0 else max value = fnc(value, line.price_unit - value_split) value_split += value if valuation.id not in towrite_dict: towrite_dict[valuation.id] = value else: towrite_dict[valuation.id] += value if towrite_dict: for key, value in towrite_dict.items(): AdjustementLines.browse(key).write({'additional_landed_cost': value}) return True
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 _get_check_amount_in_words(self, amount): # TODO: merge, refactor and complete the amount_to_text and amount_to_text_en classes check_amount_in_words = amount_to_text_en.amount_to_text(math.floor(amount), lang='en', currency='') check_amount_in_words = check_amount_in_words.replace(' and Zero Cent', '') # Ugh decimals = amount % 1 if decimals >= 10**-2: check_amount_in_words += _(' and %s/100') % str(int(round(float_round(decimals*100, precision_rounding=1)))) return check_amount_in_words
def AmountToTextFractional(amountInt): amount_word = amount_to_text_en.amount_to_text( math.floor(amountInt), lang='en', currency='') amount_word = amount_word.replace(' and Zero Cent', '') decimals = amountInt % 1 if decimals >= 10**-2: amount_word += _(' and %s/100') % str(int(round( float_round(decimals*100, precision_rounding=1)))) return amount_word
def _onchange_amount(self): if hasattr(super(AccountPayment, self), '_onchange_amount'): super(AccountPayment, self)._onchange_amount() check_amount_in_words = amount_to_text_en.amount_to_text(math.floor(self.amount), lang='en', currency='') check_amount_in_words = check_amount_in_words.replace(' and Zero Cent', '') # Ugh decimals = self.amount % 1 if decimals >= 10**-2: check_amount_in_words += _(' and %s/100') % str(int(round(float_round(decimals*100, precision_rounding=1)))) self.check_amount_in_words = check_amount_in_words
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.routing_id, float_round(qty / bom.product_qty, precision_rounding=1, rounding_method='UP'), level) values = { 'bom_id': bom_id, 'currency': self.env.user.company_id.currency_id, 'operations': lines, } return self.env.ref('mrp.report_mrp_operation_line').render({'data': values})
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 convert_to_month(value): return float_round(value / 12.0, precision_rounding=0.01, rounding_method='DOWN')
def _get_rounded_amount(self, amount): if self.config_id.cash_rounding: amount = float_round(amount, precision_rounding=self.config_id.rounding_method.rounding, rounding_method=self.config_id.rounding_method.rounding_method) currency = self.currency_id return currency.round(amount) if currency else amount
def compute_landed_cost(self): AdjustementLines = self.env['stock.valuation.adjustment.lines'] AdjustementLines.search([('cost_id', 'in', self.ids)]).unlink() digits = self.env['decimal.precision'].precision_get('Product Price') towrite_dict = {} for cost in self.filtered(lambda cost: cost.picking_ids): total_qty = 0.0 total_cost = 0.0 total_weight = 0.0 total_volume = 0.0 total_line = 0.0 all_val_line_values = cost.get_valuation_lines() for val_line_values in all_val_line_values: for cost_line in cost.cost_lines: val_line_values.update({ 'cost_id': cost.id, 'cost_line_id': cost_line.id }) self.env['stock.valuation.adjustment.lines'].create( val_line_values) total_qty += val_line_values.get('quantity', 0.0) total_weight += val_line_values.get('weight', 0.0) total_volume += val_line_values.get('volume', 0.0) former_cost = val_line_values.get('former_cost', 0.0) # round this because former_cost on the valuation lines is also rounded total_cost += tools.float_round( former_cost, precision_digits=digits) if digits else former_cost total_line += 1 for line in cost.cost_lines: value_split = 0.0 for valuation in cost.valuation_adjustment_lines: value = 0.0 if valuation.cost_line_id and valuation.cost_line_id.id == line.id: if line.split_method == 'by_quantity' and total_qty: per_unit = (line.price_unit / total_qty) value = valuation.quantity * per_unit elif line.split_method == 'by_weight' and total_weight: per_unit = (line.price_unit / total_weight) value = valuation.weight * per_unit elif line.split_method == 'by_volume' and total_volume: per_unit = (line.price_unit / total_volume) value = valuation.volume * per_unit elif line.split_method == 'equal': value = (line.price_unit / total_line) elif line.split_method == 'by_current_cost_price' and total_cost: per_unit = (line.price_unit / total_cost) value = valuation.former_cost * per_unit else: value = (line.price_unit / total_line) if digits: value = tools.float_round(value, precision_digits=digits, rounding_method='UP') fnc = min if line.price_unit > 0 else max value = fnc(value, line.price_unit - value_split) value_split += value if valuation.id not in towrite_dict: towrite_dict[valuation.id] = value else: towrite_dict[valuation.id] += value for key, value in towrite_dict.items(): AdjustementLines.browse(key).write( {'additional_landed_cost': value}) return True
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 _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, })
'res_id': self.account_move.id, } def _is_pos_order_paid(self): return float_is_zero(self.amount_total - self.amount_paid, precision_rounding=self.currency_id.rounding) def action_pos_order_paid(self): <<<<<<< HEAD if not self._is_pos_order_paid(): ======= self.ensure_one() if not self.config_id.cash_rounding: 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) if not float_is_zero(total - self.amount_paid, precision_rounding=self.currency_id.rounding): >>>>>>> f0a66d05e70e432d35dc68c9fb1e1cc6e51b40b8 raise UserError(_("Order %s is not fully paid.") % self.name) self.write({'state': 'paid'}) return True def _get_amount_receivable(self): return self.amount_total def _prepare_invoice_vals(self): self.ensure_one()
def test_08_cash_basis_multicurrency_payment_after_invoice(self): """Test to validate tax effectively Payable My company currency is MXN. Invoice issued two days ago in USD at a rate => 1MXN = 0.80 USD. Booked like: Expenses 1250 1000 USD Unpaid Taxes 200 160 USD Payable 1450 -1160 USD Payment issued today in USD at a rate => 1 MXN = 1.25 USD. Booked like: Payable 928 1160 USD Bank 928 -1160 USD This Generates a Exchange Rate Difference. Booked like: Payable 522 0 USD Gain Exchange rate 522 0 USD And a Tax Cash Basis Entry is generated. Booked like: Tax Base Account 800 1000 USD Tax Base Account 800 -1000 USD Creditable Tax 128 160 USD Unpaid Taxes 128 -160 USD What I expect from here: - Base to report to DIOT: Tax Base Account MXN 800.00 - Creditable Tax MXN 128.00 - Have a difference of MXN 72.00 for Unpaid Taxes that I would later have to issue as a Loss in Exchange Rate Difference Loss Exchange rate 72 0 USD Unpaid Taxes 72 0 USD """ invoice_date = self.two_days_ago self.company.partner_id.write({ 'property_account_position_id': self.fiscal_position.id, }) invoice_id = self.create_invoice( 1000, invoice_date, inv_type='in_invoice', currency_id=self.usd.id) self.create_payment( invoice_id, self.today, 1160, self.bank_journal_usd, self.usd) # noqa base_amls = self.account_move_line_model.search( [('account_id', '=', self.account_tax_cash_basis.id)]) base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance')) self.assertEquals( float_round(base_at_payment, precision_digits=self.precision), 800) tax_amls = self.account_move_line_model.search( [('account_id', '=', self.tax_account.id)]) tax_diff = sum(tax_amls.mapped('balance')) self.assertEquals( float_round(tax_diff, precision_digits=self.precision), 72)
def record_production(self): self.ensure_one() if self.qty_producing < 0: raise UserError( _('Please set the quantity you produced in the Current Qty field. It can not be 0!' )) if (self.production_id.product_id.tracking != 'none') and not self.final_lot_id: raise UserError( _('You should provide a lot for the final product')) # Update quantities done on each raw material line raw_moves = self.move_raw_ids.filtered( lambda x: (x.has_tracking == 'none') and (x.state not in ('done', 'cancel')) and x.bom_line_id) for move in raw_moves: if move.unit_factor: rounding = move.product_uom.rounding move.quantity_done += float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding) # Transfer quantities from temporary to final move lots or make them final for move_lot in self.active_move_lot_ids: # Check if move_lot already exists if move_lot.quantity_done <= 0: # rounding... move_lot.sudo().unlink() continue if not move_lot.lot_id: raise UserError(_('You should provide a lot for a component')) # Search other move_lot where it could be added: lots = self.move_lot_ids.filtered( lambda x: (x.lot_id.id == move_lot.lot_id.id) and (not x.lot_produced_id) and (not x.done_move)) if lots: lots[0].quantity_done += move_lot.quantity_done lots[0].lot_produced_id = self.final_lot_id.id move_lot.sudo().unlink() else: move_lot.lot_produced_id = self.final_lot_id.id move_lot.done_wo = True # One a piece is produced, you can launch the next work order if self.next_work_order_id.state == 'pending': self.next_work_order_id.state = 'ready' if self.next_work_order_id and self.final_lot_id and not self.next_work_order_id.final_lot_id: self.next_work_order_id.final_lot_id = self.final_lot_id.id self.move_lot_ids.filtered( lambda move_lot: not move_lot.done_move and not move_lot. lot_produced_id and move_lot.quantity_done > 0).write({ 'lot_produced_id': self.final_lot_id.id, 'lot_produced_qty': self.qty_producing }) # If last work order, then post lots used # TODO: should be same as checking if for every workorder something has been done? if not self.next_work_order_id: production_moves = self.production_id.move_finished_ids.filtered( lambda x: (x.state not in ('done', 'cancel'))) for production_move in production_moves: if production_move.product_id.id == self.production_id.product_id.id and production_move.product_id.tracking != 'none': move_lot = production_move.move_lot_ids.filtered( lambda x: x.lot_id.id == self.final_lot_id.id) if move_lot: move_lot.quantity += self.qty_producing move_lot.quantity_done += self.qty_producing else: move_lot.create({ 'move_id': production_move.id, 'lot_id': self.final_lot_id.id, 'quantity': self.qty_producing, 'quantity_done': self.qty_producing, 'workorder_id': self.id, }) elif production_move.unit_factor: rounding = production_move.product_uom.rounding production_move.quantity_done += float_round( self.qty_producing * production_move.unit_factor, precision_rounding=rounding) else: production_move.quantity_done += self.qty_producing # TODO: UoM conversion? # Update workorder quantity produced self.qty_produced += self.qty_producing # Set a qty producing if self.qty_produced >= self.production_id.product_qty: self.qty_producing = 0 elif self.production_id.product_id.tracking == 'serial': self.qty_producing = 1.0 self._generate_lot_ids() else: self.qty_producing = self.production_id.product_qty - self.qty_produced self._generate_lot_ids() if self.qty_produced >= self.production_id.product_qty: self.button_finish() return True
def _onchange_product_qty(self): lines = [] qty_todo = self.product_uom_id._compute_quantity( self.product_qty, self.production_id.product_uom_id, round=False) for move in self.production_id.move_raw_ids.filtered( lambda m: m.state not in ('done', 'cancel') and m.bom_line_id): qty_to_consume = float_round( qty_todo * move.unit_factor, precision_rounding=move.product_uom.rounding) for move_line in move.move_line_ids: if float_compare( qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) <= 0: break if move_line.lot_produced_id or float_compare( move_line.product_uom_qty, move_line.qty_done, precision_rounding=move.product_uom.rounding) <= 0: continue to_consume_in_line = min(qty_to_consume, move_line.product_uom_qty) lines.append({ 'move_id': move.id, 'qty_to_consume': to_consume_in_line, 'qty_done': to_consume_in_line, 'lot_id': move_line.lot_id.id, 'product_uom_id': move.product_uom.id, 'product_id': move.product_id.id, 'qty_reserved': min(to_consume_in_line, move_line.product_uom_qty), }) qty_to_consume -= to_consume_in_line if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0: if move.product_id.tracking == 'serial': while float_compare( qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0: lines.append({ 'move_id': move.id, 'qty_to_consume': 1, 'qty_done': 1, 'product_uom_id': move.product_uom.id, 'product_id': move.product_id.id, }) qty_to_consume -= 1 else: lines.append({ 'move_id': move.id, 'qty_to_consume': qty_to_consume, 'qty_done': qty_to_consume, 'product_uom_id': move.product_uom.id, 'product_id': move.product_id.id, }) self.produce_line_ids = [(0, 0, x) for x in lines]
def _compute_qty_remaining(self): for wo in self: wo.qty_remaining = float_round( wo.qty_production - wo.qty_produced, precision_rounding=wo.production_id.product_uom_id.rounding)
def record_production(self): if not self: return True self.ensure_one() if self.qty_producing <= 0: raise UserError( _('Please set the quantity you are currently producing. It should be different from zero.' )) if (self.production_id.product_id.tracking != 'none') and not self.final_lot_id and self.move_raw_ids: raise UserError( _('You should provide a lot/serial number for the final product.' )) # Update quantities done on each raw material line # For each untracked component without any 'temporary' move lines, # (the new workorder tablet view allows registering consumed quantities for untracked components) # we assume that only the theoretical quantity was used for move in self.move_raw_ids: if move.has_tracking == 'none' and (move.state not in ('done', 'cancel')) and move.bom_line_id\ and move.unit_factor and not move.move_line_ids.filtered(lambda ml: not ml.done_wo): rounding = move.product_uom.rounding if self.product_id.tracking != 'none': qty_to_add = float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding) move._generate_consumed_move_line(qty_to_add, self.final_lot_id) elif len(move._get_move_lines()) < 2: move.quantity_done += float_round( self.qty_producing * move.unit_factor, precision_rounding=rounding) else: move._set_quantity_done( move.quantity_done + float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding)) # Transfer quantities from temporary to final move lots or make them final for move_line in self.active_move_line_ids: # Check if move_line already exists if move_line.qty_done <= 0: # rounding... move_line.sudo().unlink() continue if move_line.product_id.tracking != 'none' and not move_line.lot_id: raise UserError( _('You should provide a lot/serial number for a component.' )) # Search other move_line where it could be added: lots = self.move_line_ids.filtered( lambda x: (x.lot_id.id == move_line.lot_id.id) and (not x.lot_produced_id) and (not x.done_move) and (x.product_id == move_line.product_id)) if lots: lots[0].qty_done += move_line.qty_done lots[0].lot_produced_id = self.final_lot_id.id self._link_to_quality_check(move_line, lots[0]) move_line.sudo().unlink() else: move_line.lot_produced_id = self.final_lot_id.id move_line.done_wo = True self.move_line_ids.filtered( lambda move_line: not move_line.done_move and not move_line. lot_produced_id and move_line.qty_done > 0).write({ 'lot_produced_id': self.final_lot_id.id, 'lot_produced_qty': self.qty_producing }) # If last work order, then post lots used # TODO: should be same as checking if for every workorder something has been done? if not self.next_work_order_id: production_move = self.production_id.move_finished_ids.filtered( lambda x: (x.product_id.id == self.production_id.product_id.id ) and (x.state not in ('done', 'cancel'))) if production_move.product_id.tracking != 'none': move_line = production_move.move_line_ids.filtered( lambda x: x.lot_id.id == self.final_lot_id.id) if move_line: move_line.product_uom_qty += self.qty_producing move_line.qty_done += self.qty_producing else: location_dest_id = production_move.location_dest_id.get_putaway_strategy( self.product_id ).id or production_move.location_dest_id.id move_line.create({ 'move_id': production_move.id, 'product_id': production_move.product_id.id, 'lot_id': self.final_lot_id.id, 'product_uom_qty': self.qty_producing, 'product_uom_id': production_move.product_uom.id, 'qty_done': self.qty_producing, 'workorder_id': self.id, 'location_id': production_move.location_id.id, 'location_dest_id': location_dest_id, }) else: production_move._set_quantity_done(self.qty_producing) if not self.next_work_order_id: for by_product_move in self._get_byproduct_move_to_update(): if by_product_move.has_tracking != 'serial': values = self._get_byproduct_move_line( by_product_move, self.qty_producing * by_product_move.unit_factor) self.env['stock.move.line'].create(values) elif by_product_move.has_tracking == 'serial': qty_todo = by_product_move.product_uom._compute_quantity( self.qty_producing * by_product_move.unit_factor, by_product_move.product_id.uom_id) for i in range( 0, int(float_round(qty_todo, precision_digits=0))): values = self._get_byproduct_move_line( by_product_move, 1) self.env['stock.move.line'].create(values) # Update workorder quantity produced self.qty_produced += self.qty_producing if self.final_lot_id: self.final_lot_id.use_next_on_work_order_id = self.next_work_order_id self.final_lot_id = False # One a piece is produced, you can launch the next work order self._start_nextworkorder() # Set a qty producing rounding = self.production_id.product_uom_id.rounding if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0: self.qty_producing = 0 elif self.production_id.product_id.tracking == 'serial': self._assign_default_final_lot_id() self.qty_producing = 1.0 self._generate_lot_ids() else: self.qty_producing = float_round(self.production_id.product_qty - self.qty_produced, precision_rounding=rounding) self._generate_lot_ids() if self.next_work_order_id and self.next_work_order_id.state not in [ 'done', 'cancel' ] and self.production_id.product_id.tracking != 'none': self.next_work_order_id._assign_default_final_lot_id() if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0: self.button_finish() return True
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 test_11_invoice_not_company_currency_payment_in_company_currency(self): """Test to validate tax effectively Payable My company currency is MXN. Invoice issued yesterday in USD at a rate => 1MXN = 1 USD. Booked like: Expenses 1000 1000 USD Unpaid Taxes 160 160 USD Payable 1160 -1160 USD Payment issued today in MXN at a rate => 1 MXN = 1.25 USD. Booked like: Payable 928 - - Bank 928 - - This Generates a Exchange Rate Difference. Booked like: Payable 232 232 USD Gain Exchange rate 522 -232 USD And a Tax Cash Basis Entry is generated. Booked like: Tax Base Account 800 0 USD Tax Base Account 800 0 USD Creditable Tax 128 0 USD # (I'd expect the same value as in the invoice for amount_currency in tax: 160 USD) # noqa Unpaid Taxes 128 0 USD What I expect from here: - Base to report to DIOT: Tax Base Account MXN 800.00 - Creditable Tax MXN 128.00 - Have a difference of MXN 32.00 for Unpaid Taxes that I would later have to issue as a Loss in Exchange Rate Difference Loss Exchange rate 32 0 USD Unpaid Taxes 32 0 USD """ invoice_date = self.yesterday self.company.partner_id.write({ 'property_account_position_id': self.fiscal_position.id, }) invoice_id = self.create_invoice( 1000, invoice_date, inv_type='in_invoice', currency_id=self.usd.id) self.create_payment( invoice_id, self.today, 928, self.bank_journal_mxn, self.mxn) # noqa # Testing that I am fetching the right Tax Base base_amls = self.account_move_line_model.search( [('account_id', '=', self.account_tax_cash_basis.id)]) base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance')) self.assertEquals( float_round(base_at_payment, precision_digits=self.precision), 800) # Testing that I am fetching the right difference in Exchange rate tax_amls = self.account_move_line_model.search( [('account_id', '=', self.tax_account.id)]) tax_diff = sum(tax_amls.mapped('balance')) self.assertEquals( float_round(tax_diff, precision_digits=self.precision), 32)
def test_05_invoice_not_company_currency_payment_in_company_currency(self): """Test to validate tax effectively receivable My company currency is MXN. Invoice issued yesterday in USD at a rate => 1MXN = 1 USD. Booked like: Receivable 1160 1160 USD Revenue 1000 -1000 USD Taxes to Collect 160 -160 USD Payment issued today in MXN at a rate => 1 MXN = 1.25 USD. Booked like: Bank 928 - - Receivable 928 - - This Generates a Exchange Rate Difference. Booked like: Loss Exchange rate 232 232 USD Receivable 232 -232 USD And a Tax Cash Basis Entry is generated. Booked like: Tax Base Account 800 0 USD Tax Base Account 800 0 USD Taxes to Collect 128 0 USD # (I'd expect the same value as in the invoice for amount_currency in tax: 160 USD) # noqa Taxes to Paid 128 0 USD What I expect from here: - Base to report to DIOT if it would be the case (not in this case): * Tax Base Account MXN 800.00 - Paid to SAT MXN 128.00 - Have a difference of MXN -32.00 for Taxes to Collect that I would later have to issue as a Gain in Exchange Rate Difference Taxes to Collect 32 0 USD Gain Exchange rate 32 0 USD """ invoice_date = self.yesterday self.company.partner_id.write({ 'property_account_position_id': self.fiscal_position.id, }) invoice_id = self.create_invoice( 1000, invoice_date, currency_id=self.usd.id) self.assertEqual(invoice_id.state, "open") self.assertEqual( invoice_id.invoice_line_ids.invoice_line_tax_ids. l10n_mx_cfdi_tax_type, "Tasa") self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed", invoice_id.message_ids.mapped('body')) xml = invoice_id.l10n_mx_edi_get_xml_etree() self.assertEqual(invoice_id.amount_total, float(xml.get('Total')), "Total amount is not right") self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed", invoice_id.message_ids.mapped('body')) self.create_payment( invoice_id, self.today, 928, self.bank_journal_mxn, self.mxn) # noqa # Testing that I am fetching the right Tax Base base_amls = self.account_move_line_model.search( [('account_id', '=', self.account_tax_cash_basis.id)]) base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance')) self.assertEquals( float_round(base_at_payment, precision_digits=self.precision), -800) # Testing that I am fetching the right difference in Exchange rate tax_amls = self.account_move_line_model.search( [('account_id', '=', self.tax_account.id)]) tax_diff = sum(tax_amls.mapped('balance')) self.assertEquals( float_round(tax_diff, precision_digits=self.precision), -32)
def _get_supplierinfo_pricelist_price(self, rule, date=None, quantity=None, product_id=None): """Method for getting the price from supplier info.""" self.ensure_one() if product_id: domain = [ '|', ('product_id', '=', product_id), ('product_tmpl_id', '=', self.id), ] else: domain = [ ('product_tmpl_id', '=', self.id), ] if not rule.no_supplierinfo_min_quantity and quantity: domain += [ '|', ('min_qty', '=', False), ('min_qty', '<=', quantity), ] if date: domain += [ '|', ('date_start', '=', False), ('date_start', '<=', date), '|', ('date_end', '=', False), ('date_end', '>=', date), ] # We use a different default order because we are interested in getting # the price for lowest minimum quantity if no_supplierinfo_min_quantity supplierinfos = self.env['product.supplierinfo'].search( domain, order='min_qty,sequence,price', ) if rule.no_supplierinfo_min_quantity: price = supplierinfos[:1].price else: price = supplierinfos[-1:].price if price: # We have to replicate this logic in this method as pricelist # method are atomic and we can't hack inside. # Verbatim copy of part of product.pricelist._compute_price_rule. qty_uom_id = self._context.get('uom') or self.uom_id.id price_uom = self.env['product.uom'].browse([qty_uom_id]) convert_to_price_uom = ( lambda price: self.uom_id._compute_price(price, price_uom)) price_limit = price price = (price - (price * (rule.price_discount / 100))) or 0.0 if rule.price_round: price = tools.float_round(price, precision_rounding=rule.price_round) if rule.price_surcharge: price_surcharge = convert_to_price_uom(rule.price_surcharge) price += price_surcharge if rule.price_min_margin: price_min_margin = convert_to_price_uom(rule.price_min_margin) price = max(price, price_limit + price_min_margin) if rule.price_max_margin: price_max_margin = convert_to_price_uom(rule.price_max_margin) price = min(price, price_limit + price_max_margin) return price
def test_01_cash_basis_multicurrency_payment_before_invoice(self): """Test to validate tax effectively receivable My company currency is MXN. Invoice issued yesterday in USD at a rate => 1MXN = 1 USD. Booked like: Receivable 1160 1160 USD Revenue 1000 -1000 USD Taxes to Collect 160 -160 USD Payment issued two days ago in USD at a rate => 1MXN = 0.80 USD. Booked like: Bank 1450 1160 USD Receivable 1450 -1160 USD This Generates a Exchange Rate Difference. Booked like: Receivable 290 0 USD Gain Exchange rate 290 0 USD And a Tax Cash Basis Entry is generated. Booked like: Tax Base Account 1250 1000 USD Tax Base Account 1250 -1000 USD Taxes to Collect 200 160 USD Taxes to Paid 200 -160 USD What I expect from here: - Base to report to DIOT if it would be the case (not in this case): * Tax Base Account MXN 1250.00 - Paid to SAT MXN 200.00 - Have a difference of MXN 40.00 for Taxes to Collect that I would later have to issue as a Loss in Exchange Rate Difference Loss Exchange rate 40 0 USD Taxes to Collect 40 0 USD """ invoice_date = self.yesterday self.company.partner_id.write({ 'property_account_position_id': self.fiscal_position.id, }) invoice_id = self.create_invoice( 1000, invoice_date, currency_id=self.usd.id) self.assertEqual(invoice_id.state, "open") self.assertEqual( invoice_id.invoice_line_ids.invoice_line_tax_ids. l10n_mx_cfdi_tax_type, "Tasa") self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed", invoice_id.message_ids.mapped('body')) xml = invoice_id.l10n_mx_edi_get_xml_etree() self.assertEqual(invoice_id.amount_total, float(xml.get('Total')), "Total amount is not right") self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed", invoice_id.message_ids.mapped('body')) self.create_payment( invoice_id, self.two_days_ago, 1160, self.bank_journal_usd, self.usd) # noqa base_amls = self.account_move_line_model.search( [('account_id', '=', self.account_tax_cash_basis.id)]) base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance')) self.assertEquals( float_round(base_at_payment, precision_digits=self.precision), -1250) tax_amls = self.account_move_line_model.search( [('account_id', '=', self.tax_account.id)]) tax_diff = sum(tax_amls.mapped('balance')) self.assertEquals( float_round(tax_diff, precision_digits=self.precision), 40)
def test_06_invoice_company_currency_payment_not_company_currency(self): """Test to validate tax effectively receivable My company currency is MXN. Invoice issued yesterday in MXN at a rate => 1MXN = 1 USD. Booked like: Receivable 1160 - - Revenue 1000 - - Taxes to Collect 160 - - Payment issued today in MXN at a rate => 1 MXN = 1.25 USD. Booked like: Bank 1160 - - Receivable 1160 - - This does not generates any Exchange Rate Difference. But a Tax Cash Basis Entry is generated. Booked like: Tax Base Account 1000 - - Tax Base Account 1000 - - Taxes to Collect 160 - - Taxes to Paid 160 - - What I expect from here: - Base to report to DIOT if it would be the case (not in this case): * Tax Base Account MXN 1000.00 - Paid to SAT MXN 160.00 - Have no difference for Taxes to Collect """ invoice_date = self.yesterday self.company.partner_id.write({ 'property_account_position_id': self.fiscal_position.id, }) invoice_id = self.create_invoice( 1000, invoice_date, currency_id=self.company.currency_id.id) self.assertEqual(invoice_id.state, "open") self.assertEqual( invoice_id.invoice_line_ids.invoice_line_tax_ids. l10n_mx_cfdi_tax_type, "Tasa") self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed", invoice_id.message_ids.mapped('body')) xml = invoice_id.l10n_mx_edi_get_xml_etree() self.assertEqual(invoice_id.amount_total, float(xml.get('Total')), "Total amount is not right") self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed", invoice_id.message_ids.mapped('body')) self.create_payment( invoice_id, self.today, 1160, self.bank_journal_mxn, self.mxn) # noqa base_amls = self.account_move_line_model.search( [('account_id', '=', self.account_tax_cash_basis.id)]) base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance')) self.assertEquals( float_round(base_at_payment, precision_digits=self.precision), -1000) tax_amls = self.account_move_line_model.search( [('account_id', '=', self.tax_account.id)]) tax_diff = sum(tax_amls.mapped('balance')) self.assertEquals( float_round(tax_diff, precision_digits=self.precision), 0)
def _create_invoice(self, move_vals): self.ensure_one() new_move = self.env['account.move'].sudo().with_company(self.company_id).with_context(default_move_type=move_vals['move_type']).create(move_vals) message = _("This invoice has been created from the point of sale session: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>") % (self.id, self.name) new_move.message_post(body=message) if self.config_id.cash_rounding: rounding_applied = float_round(self.amount_paid - self.amount_total, precision_rounding=new_move.currency_id.rounding) rounding_line = new_move.line_ids.filtered(lambda line: line.is_rounding_line) if rounding_applied: if rounding_applied > 0.0: account_id = new_move.invoice_cash_rounding_id.loss_account_id.id else: account_id = new_move.invoice_cash_rounding_id.profit_account_id.id if rounding_line: if rounding_line.debit > 0: rounding_line_difference = rounding_line.debit + rounding_applied else: rounding_line_difference = -rounding_line.credit + rounding_applied if rounding_line_difference: rounding_line.with_context(check_move_validity=False).write({ 'debit': rounding_applied < 0.0 and -rounding_applied or 0.0, 'credit': rounding_applied > 0.0 and rounding_applied or 0.0, 'account_id': account_id, 'price_unit': rounding_applied, }) existing_terms_line = new_move.line_ids.filtered( lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')) if existing_terms_line.debit > 0: existing_terms_line_new_val = float_round( existing_terms_line.debit + rounding_line_difference, precision_rounding=new_move.currency_id.rounding) else: existing_terms_line_new_val = float_round( -existing_terms_line.credit + rounding_line_difference, precision_rounding=new_move.currency_id.rounding) existing_terms_line.write({ 'debit': existing_terms_line_new_val > 0.0 and existing_terms_line_new_val or 0.0, 'credit': existing_terms_line_new_val < 0.0 and -existing_terms_line_new_val or 0.0, }) new_move._recompute_payment_terms_lines() else: self.env['account.move.line'].create({ 'debit': rounding_applied < 0.0 and -rounding_applied or 0.0, 'credit': rounding_applied > 0.0 and rounding_applied or 0.0, 'quantity': 1.0, 'amount_currency': rounding_applied, 'partner_id': new_move.partner_id.id, 'move_id': new_move.id, 'currency_id': new_move.currency_id if new_move.currency_id != new_move.company_id.currency_id else False, 'company_id': new_move.company_id.id, 'company_currency_id': new_move.company_id.currency_id.id, 'is_rounding_line': True, 'sequence': 9999, 'name': new_move.invoice_cash_rounding_id.name, 'account_id': account_id, }) else: if rounding_line: rounding_line.unlink() return new_move
def try_round(amount, expected, precision_rounding=None): value = float_round(amount, precision_rounding=precision_rounding) result = float_repr(value, precision_digits=2) self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected))
def float_round_custom(value, precision_digits=None, precision_rounding=None, rounding_method='HALF-UP'): result = float_round(value, precision_digits, precision_rounding, rounding_method) if precision_rounding == 1: return int(result) return result
def test_14_cash_basis_multicurrency_creditnote_after_invoice(self): """Test to validate tax effectively receivable My company currency is MXN. Invoice issued two days ago in USD at a rate => 1MXN = 0.80 USD. Booked like: Receivable 1450 1160 USD Revenue 1250 -1000 USD Taxes to Collect 200 -160 USD Credit Note issued today in USD at a rate => 1 MXN = 1.25 USD. Booked like: Revenue 800 1000 USD Taxes to Collect 128 160 USD Receivable 928 -1160 USD This Generates a Exchange Rate Difference. Booked like: Loss Exchange rate 522 0 USD Receivable 522 0 USD And two Tax Cash Basis Entry are generated. Booked like: Tax Base Account 800 1000 USD Tax Base Account 800 -1000 USD Taxes to Collect 128 160 USD Taxes to Paid 128 -160 USD Tax Base Account 800 1000 USD Tax Base Account 800 -1000 USD Taxes to Paid 128 160 USD Taxes to Collect 128 -160 USD What I expect from here: - Base to report to DIOT if it would be the case (not in this case): * Tax Base Account MXN 800.00 and MXN -800.00 - Paid to SAT MXN 0.00 - Have a difference of MXN -72.00 for Taxes to Collect that I would later have to issue as a Gain in Exchange Rate Difference Taxes to Collect 72 0 USD Gain Exchange rate 72 0 USD """ invoice_date = self.two_days_ago self.company.partner_id.write({ 'property_account_position_id': self.fiscal_position.id, }) invoice_id = self.create_invoice( 1000, invoice_date, currency_id=self.usd.id) self.assertEqual(invoice_id.state, "posted") refund = self.env['account.move.reversal'].with_context(active_ids=invoice_id.ids).create({ 'refund_method': 'refund', 'reason': 'Refund Test', 'date': self.today, }) result = refund.reverse_moves() refund_id = result.get('domain')[1][2] refund = self.env['account.move'].browse(refund_id) refund.refresh() refund.post() self.assertEqual(refund.state, "open") ((invoice_id | refund) .mapped('move_id.line_ids') .filtered(lambda l: l.account_id.user_type_id.type == 'receivable') .reconcile()) base_amls = self.account_move_line_model.search( [('account_id', '=', self.account_tax_cash_basis.id)]) base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance')) self.assertEquals( float_round(base_at_payment, precision_digits=self.precision), 0) tax_amls = self.account_move_line_model.search( [('account_id', '=', self.tax_account.id)]) tax_diff = sum(tax_amls.mapped('balance')) self.assertEquals( float_round(tax_diff, precision_digits=self.precision), -72)
def _get_bom(self, bom_id=False, product_id=False, line_qty=False, line_id=False, level=False): bom = self.env['mrp.bom'].browse(bom_id) bom_quantity = line_qty if line_id: current_line = self.env['mrp.bom.line'].browse(int(line_id)) bom_quantity = current_line.product_uom_id._compute_quantity( line_qty, bom.product_uom_id) # Display bom components for current selected product variant if product_id: product = self.env['product.product'].browse(int(product_id)) else: product = bom.product_id or bom.product_tmpl_id.product_variant_id if product: attachments = self.env['mrp.document'].search([ '|', '&', ('res_model', '=', 'product.product'), ('res_id', '=', product.id), '&', ('res_model', '=', 'product.template'), ('res_id', '=', product.product_tmpl_id.id) ]) else: product = bom.product_tmpl_id attachments = self.env['mrp.document'].search([ ('res_model', '=', 'product.template'), ('res_id', '=', product.id) ]) operations = self._get_operation_line( bom.routing_id, float_round(bom_quantity / bom.product_qty, precision_rounding=1, rounding_method='UP'), 0) company = bom.company_id or self.env.company lines = { 'bom': bom, 'bom_qty': bom_quantity, 'bom_prod_name': product.display_name, 'currency': company.currency_id, 'product': product, 'code': bom and bom.display_name or '', 'price': product.uom_id._compute_price( product.with_context(force_company=company.id).standard_price, bom.product_uom_id) * bom_quantity, 'total': sum([op['total'] for op in operations]), 'level': level or 0, 'operations': operations, 'operations_cost': sum([op['total'] for op in operations]), 'attachments': attachments, 'operations_time': sum([op['duration_expected'] for op in operations]) } components, total = self._get_bom_lines(bom, bom_quantity, product, line_id, level) lines['components'] = components lines['total'] += total return lines
def round_by_tools(amount): return tools.float_round(amount, precision_rounding=0.01)
def _compute_price_rule(self, products_qty_partner, date=False, uom_id=False): """ Low-level method - Mono pricelist, multi products Returns: dict{product_id: (price, suitable_rule) for the given pricelist} If date in context: Date of the pricelist (%Y-%m-%d) :param products_qty_partner: list of typles products, quantity, partner :param datetime date: validity date :param ID uom_id: intermediate unit of measure """ self.ensure_one() if not date: date = self._context.get('date') or fields.Date.context_today(self) if not uom_id and self._context.get('uom'): uom_id = self._context['uom'] if uom_id: # rebrowse with uom if given products = [ item[0].with_context(uom=uom_id) for item in products_qty_partner ] products_qty_partner = [ (products[index], data_struct[1], data_struct[2]) for index, data_struct in enumerate(products_qty_partner) ] else: products = [item[0] for item in products_qty_partner] if not products: return {} categ_ids = {} for p in products: categ = p.categ_id while categ: categ_ids[categ.id] = True categ = categ.parent_id categ_ids = list(categ_ids) is_product_template = products[0]._name == "product.template" if is_product_template: prod_tmpl_ids = [tmpl.id for tmpl in products] # all variants of all products prod_ids = [ p.id for p in list( chain.from_iterable( [t.product_variant_ids for t in products])) ] else: prod_ids = [product.id for product in products] prod_tmpl_ids = [ product.product_tmpl_id.id for product in products ] # Load all rules self._cr.execute( 'SELECT item.id ' 'FROM product_pricelist_item AS item ' 'LEFT JOIN product_category AS categ ' 'ON item.categ_id = categ.id ' 'WHERE (item.product_tmpl_id IS NULL OR item.product_tmpl_id = any(%s))' 'AND (item.product_id IS NULL OR item.product_id = any(%s))' 'AND (item.categ_id IS NULL OR item.categ_id = any(%s)) ' 'AND (item.pricelist_id = %s) ' 'AND (item.date_start IS NULL OR item.date_start<=%s) ' 'AND (item.date_end IS NULL OR item.date_end>=%s)' 'ORDER BY item.applied_on, item.min_quantity desc, categ.parent_left desc', (prod_tmpl_ids, prod_ids, categ_ids, self.id, date, date)) item_ids = [x[0] for x in self._cr.fetchall()] items = self.env['product.pricelist.item'].browse(item_ids) results = {} for product, qty, partner in products_qty_partner: results[product.id] = 0.0 suitable_rule = False # Final unit price is computed according to `qty` in the `qty_uom_id` UoM. # An intermediary unit price may be computed according to a different UoM, in # which case the price_uom_id contains that UoM. # The final price will be converted to match `qty_uom_id`. qty_uom_id = self._context.get('uom') or product.uom_id.id price_uom_id = product.uom_id.id qty_in_product_uom = qty if qty_uom_id != product.uom_id.id: try: qty_in_product_uom = self.env['product.uom'].browse([ self._context['uom'] ])._compute_quantity(qty, product.uom_id) except UserError: # Ignored - incompatible UoM in context, use default product UoM pass # if Public user try to access standard price from website sale, need to call price_compute. # TDE SURPRISE: product can actually be a template price = product.price_compute('list_price')[product.id] if self.customer_type: if 'wholesale' in self.customer_type.name.lower(): if product.price_compute('ws_price')[product.id] > 0: price = product.price_compute('ws_price')[product.id] price_uom = self.env['product.uom'].browse([qty_uom_id]) for rule in items: if rule.min_quantity and qty_in_product_uom < rule.min_quantity: continue if is_product_template: if rule.product_tmpl_id and product.id != rule.product_tmpl_id.id: continue if rule.product_id and not ( product.product_variant_count == 1 and product.product_variant_id.id == rule.product_id.id): # product rule acceptable on template if has only one variant continue else: if rule.product_tmpl_id and product.product_tmpl_id.id != rule.product_tmpl_id.id: continue if rule.product_id and product.id != rule.product_id.id: continue if rule.categ_id: cat = product.categ_id while cat: if cat.id == rule.categ_id.id: break cat = cat.parent_id if not cat: continue if rule.brand_id: brand_ = product.product_tmpl_id.product_brand_id while brand_: if brand_.id == rule.brand_id.id: break if not brand_: continue if rule.base == 'pricelist' and rule.base_pricelist_id: price_tmp = rule.base_pricelist_id._compute_price_rule([ (product, qty, partner) ])[product.id][0] # TDE: 0 = price, 1 = rule price = rule.base_pricelist_id.currency_id.compute( price_tmp, self.currency_id, round=False) else: # if base option is public price take sale price else cost price of product # price_compute returns the price in the context UoM, i.e. qty_uom_id # reroute the computetion to wholesale price computation function if customer is wholesale if self.customer_type and 'wholesale' in self.customer_type.name.lower( ): price = product.wholesaleprice_compute( rule.base)[product.id] else: price = product.price_compute(rule.base)[product.id] convert_to_price_uom = (lambda price: product.uom_id. _compute_price(price, price_uom)) if price is not False: if rule.compute_price == 'fixed': price = convert_to_price_uom(rule.fixed_price) elif rule.compute_price == 'percentage': price = (price - (price * (rule.percent_price / 100))) or 0.0 else: # complete formula price_limit = price price = (price - (price * (rule.price_discount / 100))) or 0.0 if rule.price_round: price = tools.float_round( price, precision_rounding=rule.price_round) if rule.price_surcharge: price_surcharge = convert_to_price_uom( rule.price_surcharge) price += price_surcharge if rule.price_min_margin: price_min_margin = convert_to_price_uom( rule.price_min_margin) price = max(price, price_limit + price_min_margin) if rule.price_max_margin: price_max_margin = convert_to_price_uom( rule.price_max_margin) price = min(price, price_limit + price_max_margin) suitable_rule = rule break # Final price conversion into pricelist currency if suitable_rule and suitable_rule.compute_price != 'fixed' and suitable_rule.base != 'pricelist': price = product.currency_id.compute(price, self.currency_id, round=False) results[product.id] = (price, suitable_rule and suitable_rule.id or False) return results
def _compute_price_rule(self, products_qty_partner, date=False, uom_id=False): """ Low-level method - Mono pricelist, multi products Returns: dict{product_id: (price, suitable_rule) for the given pricelist} Date in context can be a date, datetime, ... :param products_qty_partner: list of typles products, quantity, partner :param datetime date: validity date :param ID uom_id: intermediate unit of measure """ self.ensure_one() if not date: date = self._context.get('date') or fields.Date.today() date = fields.Date.to_date( date) # boundary conditions differ if we have a datetime if not uom_id and self._context.get('uom'): uom_id = self._context['uom'] if uom_id: # rebrowse with uom if given products = [ item[0].with_context(uom=uom_id) for item in products_qty_partner ] products_qty_partner = [ (products[index], data_struct[1], data_struct[2]) for index, data_struct in enumerate(products_qty_partner) ] else: products = [item[0] for item in products_qty_partner] if not products: return {} categ_ids = {} for p in products: categ = p.categ_id while categ: categ_ids[categ.id] = True categ = categ.parent_id categ_ids = list(categ_ids) is_product_template = products[0]._name == "product.template" if is_product_template: prod_tmpl_ids = [tmpl.id for tmpl in products] # all variants of all products prod_ids = [ p.id for p in list( chain.from_iterable( [t.product_variant_ids for t in products])) ] else: prod_ids = [product.id for product in products] prod_tmpl_ids = [ product.product_tmpl_id.id for product in products ] items = self._compute_price_rule_get_items(products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids) results = {} for product, qty, partner in products_qty_partner: results[product.id] = 0.0 suitable_rule = False # Final unit price is computed according to `qty` in the `qty_uom_id` UoM. # An intermediary unit price may be computed according to a different UoM, in # which case the price_uom_id contains that UoM. # The final price will be converted to match `qty_uom_id`. qty_uom_id = self._context.get('uom') or product.uom_id.id price_uom_id = product.uom_id.id qty_in_product_uom = qty if qty_uom_id != product.uom_id.id: try: qty_in_product_uom = self.env['uom.uom'].browse([ self._context['uom'] ])._compute_quantity(qty, product.uom_id) except UserError: # Ignored - incompatible UoM in context, use default product UoM pass # if Public user try to access standard price from website sale, need to call price_compute. # TDE SURPRISE: product can actually be a template price = product.price_compute('list_price')[product.id] price_uom = self.env['uom.uom'].browse([qty_uom_id]) for rule in items: if rule.min_quantity and qty_in_product_uom < rule.min_quantity: continue if is_product_template: if rule.product_tmpl_id and product.id != rule.product_tmpl_id.id: continue if rule.product_id and not ( product.product_variant_count == 1 and product.product_variant_id.id == rule.product_id.id): # product rule acceptable on template if has only one variant continue else: if rule.product_tmpl_id and product.product_tmpl_id.id != rule.product_tmpl_id.id: continue if rule.product_id and product.id != rule.product_id.id: continue if rule.categ_id: cat = product.categ_id while cat: if cat.id == rule.categ_id.id: break cat = cat.parent_id if not cat: continue if rule.base == 'pricelist' and rule.base_pricelist_id: price_tmp = rule.base_pricelist_id._compute_price_rule( [(product, qty, partner)], date, uom_id)[product.id][0] # TDE: 0 = price, 1 = rule price = rule.base_pricelist_id.currency_id._convert( price_tmp, self.currency_id, self.env.company, date, round=False) else: # if base option is public price take sale price else cost price of product # price_compute returns the price in the context UoM, i.e. qty_uom_id price = product.price_compute(rule.base)[product.id] convert_to_price_uom = (lambda price: product.uom_id. _compute_price(price, price_uom)) if price is not False: if rule.compute_price == 'fixed': price = convert_to_price_uom(rule.fixed_price) elif rule.compute_price == 'percentage': price = (price - (price * (rule.percent_price / 100))) or 0.0 else: # complete formula price_limit = price price = (price - (price * (rule.price_discount / 100))) or 0.0 if rule.price_round: price = tools.float_round( price, precision_rounding=rule.price_round) if rule.price_surcharge: price_surcharge = convert_to_price_uom( rule.price_surcharge) price += price_surcharge if rule.price_min_margin: price_min_margin = convert_to_price_uom( rule.price_min_margin) price = max(price, price_limit + price_min_margin) if rule.price_max_margin: price_max_margin = convert_to_price_uom( rule.price_max_margin) price = min(price, price_limit + price_max_margin) suitable_rule = rule break # Final price conversion into pricelist currency if suitable_rule and suitable_rule.compute_price != 'fixed' and suitable_rule.base != 'pricelist': if suitable_rule.base == 'standard_price': cur = product.cost_currency_id else: cur = product.currency_id price = cur._convert(price, self.currency_id, self.env.company, date, round=False) if not suitable_rule: cur = product.currency_id price = cur._convert(price, self.currency_id, self.env.company, date, round=False) results[product.id] = (price, suitable_rule and suitable_rule.id or False) return results
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, bom_type='phantom') if bom: 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 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 _procure_orderpoint_confirm(self, use_new_cursor=False, company_id=False): """ Create procurements based on orderpoints. :param bool use_new_cursor: if set, use a dedicated cursor and auto-commit after processing 1000 orderpoints. This is appropriate for batch jobs only. """ if company_id and self.env.user.company_id.id != company_id: # To ensure that the company_id is taken into account for # all the processes triggered by this method # i.e. If a PO is generated by the run of the procurements the # sequence to use is the one for the specified company not the # one of the user's company self = self.with_context(company_id=company_id, force_company=company_id) OrderPoint = self.env['stock.warehouse.orderpoint'] domain = self._get_orderpoint_domain(company_id=company_id) orderpoints_noprefetch = OrderPoint.with_context( prefetch_fields=False).search( domain, order=self._procurement_from_orderpoint_get_order()).ids while orderpoints_noprefetch: if use_new_cursor: cr = registry(self._cr.dbname).cursor() self = self.with_env(self.env(cr=cr)) OrderPoint = self.env['stock.warehouse.orderpoint'] orderpoints = OrderPoint.browse(orderpoints_noprefetch[:1000]) orderpoints_noprefetch = orderpoints_noprefetch[1000:] # Calculate groups that can be executed together location_data = defaultdict(lambda: dict( products=self.env['product.product'], orderpoints=self.env['stock.warehouse.orderpoint'], groups=list())) for orderpoint in orderpoints: key = self._procurement_from_orderpoint_get_grouping_key( [orderpoint.id]) location_data[key]['products'] += orderpoint.product_id location_data[key]['orderpoints'] += orderpoint location_data[key][ 'groups'] = self._procurement_from_orderpoint_get_groups( [orderpoint.id]) for location_id, location_data in location_data.items(): location_orderpoints = location_data['orderpoints'] product_context = dict( self._context, location=location_orderpoints[0].location_id.id) substract_quantity = location_orderpoints._quantity_in_progress( ) for group in location_data['groups']: if group.get('from_date'): product_context['from_date'] = group[ 'from_date'].strftime( DEFAULT_SERVER_DATETIME_FORMAT) if group['to_date']: product_context['to_date'] = group['to_date'].strftime( DEFAULT_SERVER_DATETIME_FORMAT) product_quantity = location_data['products'].with_context( product_context)._product_available() for orderpoint in location_orderpoints: try: op_product_virtual = product_quantity[ orderpoint.product_id.id]['virtual_available'] if op_product_virtual is None: continue if float_compare(op_product_virtual, orderpoint.product_min_qty, precision_rounding=orderpoint. product_uom.rounding) <= 0: qty = max(orderpoint.product_min_qty, orderpoint.product_max_qty ) - op_product_virtual remainder = orderpoint.qty_multiple > 0 and qty % orderpoint.qty_multiple or 0.0 if float_compare(remainder, 0.0, precision_rounding=orderpoint. product_uom.rounding) > 0: qty += orderpoint.qty_multiple - remainder if float_compare(qty, 0.0, precision_rounding=orderpoint. product_uom.rounding) < 0: continue qty -= substract_quantity[orderpoint.id] qty_rounded = float_round( qty, precision_rounding=orderpoint.product_uom. rounding) if qty_rounded > 0: values = orderpoint._prepare_procurement_values( qty_rounded, **group['procurement_values']) try: with self._cr.savepoint(): self.env['procurement.group'].run( orderpoint.product_id, qty_rounded, orderpoint.product_uom, orderpoint.location_id, orderpoint.name, orderpoint.name, values) except UserError as error: self.env[ 'stock.rule']._log_next_activity( orderpoint.product_id, error.name) self._procurement_from_orderpoint_post_process( [orderpoint.id]) if use_new_cursor: cr.commit() except OperationalError: if use_new_cursor: orderpoints_noprefetch += [orderpoint.id] cr.rollback() continue else: raise try: if use_new_cursor: cr.commit() except OperationalError: if use_new_cursor: cr.rollback() continue else: raise if use_new_cursor: cr.commit() cr.close() return {}
def move_validate(self): ''' Validate moves based on a production order. ''' moves = self._filter_closed_moves() quant_obj = self.env['stock.quant'] moves_todo = self.env['stock.move'] moves_to_unreserve = self.env['stock.move'] # Create extra moves where necessary for move in moves: # Here, the `quantity_done` was already rounded to the product UOM by the `do_produce` wizard. However, # it is possible that the user changed the value before posting the inventory by a value that should be # rounded according to the move's UOM. In this specific case, we chose to round up the value, because it # is what is expected by the user (if i consumed/produced a little more, the whole UOM unit should be # consumed/produced and the moves are split correctly). rounding = move.product_uom.rounding move.quantity_done = float_round(move.quantity_done, precision_rounding=rounding, rounding_method='UP') if move.quantity_done <= 0: continue moves_todo |= move moves_todo |= move._create_extra_move() # Split moves where necessary and move quants for move in moves_todo: rounding = move.product_uom.rounding if float_compare(move.quantity_done, move.product_uom_qty, precision_rounding=rounding) < 0: # Need to do some kind of conversion here qty_split = move.product_uom._compute_quantity( move.product_uom_qty - move.quantity_done, move.product_id.uom_id) new_move = move.split(qty_split) # If you were already putting stock.move.lots on the next one in the work order, transfer those to the new move move.move_lot_ids.filtered( lambda x: not x.done_wo or x.quantity_done == 0.0).write( {'move_id': new_move}) self.browse(new_move).quantity_done = 0.0 main_domain = [('qty', '>', 0)] preferred_domain = [('reservation_id', '=', move.id)] fallback_domain = [('reservation_id', '=', False)] fallback_domain2 = [ '&', ('reservation_id', '!=', move.id), ('reservation_id', '!=', False) ] preferred_domain_list = [preferred_domain] + [fallback_domain] + [ fallback_domain2 ] if move.has_tracking == 'none': quants = quant_obj.quants_get_preferred_domain( move.product_qty, move, domain=main_domain, preferred_domain_list=preferred_domain_list) self.env['stock.quant'].quants_move(quants, move, move.location_dest_id) else: for movelot in move.move_lot_ids: if float_compare(movelot.quantity_done, 0, precision_rounding=rounding) > 0: if not movelot.lot_id: raise UserError( _('You need to supply a lot/serial number.')) qty = move.product_uom._compute_quantity( movelot.quantity_done, move.product_id.uom_id) quants = quant_obj.quants_get_preferred_domain( qty, move, lot_id=movelot.lot_id.id, domain=main_domain, preferred_domain_list=preferred_domain_list) self.env['stock.quant'].quants_move( quants, move, move.location_dest_id, lot_id=movelot.lot_id.id) moves_to_unreserve |= move # Next move in production order if move.move_dest_id: move.move_dest_id.action_assign() moves_to_unreserve.quants_unreserve() moves_todo.write({'state': 'done', 'date': fields.Datetime.now()}) return moves_todo
def validate_taxes_on_invoice(self): company = self.company_id Param = self.env['ir.config_parameter'] api_id = Param.sudo().get_param( 'account_taxcloud.taxcloud_api_id_{}'.format(company.id) ) or Param.sudo().get_param('account_taxcloud.taxcloud_api_id') api_key = Param.sudo().get_param( 'account_taxcloud.taxcloud_api_key_{}'.format(company.id) ) or Param.sudo().get_param('account_taxcloud.taxcloud_api_key') request = TaxCloudRequest(api_id, api_key) shipper = self.company_id or self.env.user.company_id request.set_location_origin_detail(shipper) request.set_location_destination_detail(self._get_partner()) request.set_invoice_items_detail(self) response = request.get_all_taxes_values() if response.get('error_message'): raise ValidationError( _('Unable to retrieve taxes from TaxCloud: ') + '\n' + response['error_message'] + '\n\n' + _('The configuration of TaxCloud is in the Accounting app, Settings menu.' )) tax_values = response['values'] raise_warning = False for index, line in enumerate(self.invoice_line_ids): if line.price_unit >= 0.0 and line.quantity >= 0.0: price = line.price_unit * ( 1 - (line.discount or 0.0) / 100.0) * line.quantity if not price: tax_rate = 0.0 else: tax_rate = tax_values[index] / price * 100 if len(line.invoice_line_tax_ids) != 1 or float_compare( line.invoice_line_tax_ids.amount, tax_rate, precision_digits=3): raise_warning = True tax_rate = float_round(tax_rate, precision_digits=3) tax = self.env['account.tax'].sudo().search( [('amount', '=', tax_rate), ('amount_type', '=', 'percent'), ('type_tax_use', '=', 'sale')], limit=1) if not tax: tax = self.env['account.tax'].sudo().create({ 'name': 'Tax %.3f %%' % (tax_rate), 'amount': tax_rate, 'amount_type': 'percent', 'type_tax_use': 'sale', 'description': 'Sales Tax', }) line.invoice_line_tax_ids = tax self._onchange_invoice_line_ids() if self.env.context.get('taxcloud_authorize_transaction'): current_date = fields.Datetime.context_timestamp( self, datetime.datetime.now()) if self.type == 'out_invoice': request.client.service.AuthorizedWithCapture( request.api_login_id, request.api_key, request.customer_id, request.cart_id, self.id, current_date, # DateAuthorized current_date, # DateCaptured ) elif self.type == 'out_refund': request.set_invoice_items_detail(self) origin_invoice = self.env['account.invoice'].search( [('number', '=', self.origin)], limit=1) if origin_invoice: request.client.service.Returned( request.api_login_id, request.api_key, origin_invoice.id, request.cart_items, fields.Datetime.from_string(self.date_invoice)) else: _logger.warning( _("The source document on the refund is not valid and thus the refunded cart won't be logged on your taxcloud account" )) if raise_warning: return { 'warning': _('The tax rates have been updated, you may want to check it before validation' ) } else: return True