def _onchange_product_id_check_availability(self): if not self.product_id or not self.product_uom_qty or not self.product_uom: self.product_packaging = False return {} if self.product_id.type == 'product': precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') product = self.product_id.with_context( warehouse=self.order_id.warehouse_id.id) product_qty = self.product_uom._compute_quantity( self.product_uom_qty, self.product_id.uom_id) if float_compare(product.virtual_available, product_qty, precision_digits=precision) == -1: is_available = self._check_routing() if not is_available: message = _('You plan to sell %s %s but you only have %s %s available in %s warehouse.') % \ (self.product_uom_qty, self.product_uom.name, product.virtual_available, product.uom_id.name, self.order_id.warehouse_id.name) # We check if some products are available in other warehouses. if float_compare(product.virtual_available, self.product_id.virtual_available, precision_digits=precision) == -1: message += _('\nThere are %s %s available accross all warehouses.') % \ (self.product_id.virtual_available, product.uom_id.name) warning_mess = { 'title': _('Not enough inventory!'), 'message': message } return {'warning': warning_mess} return {}
def test_Clubbed_Discount_coupon_code(self): if not self.sale_order_6.coupon_flag: self.check_all_coupon_code(self.sale_order_6, 'CD15Per', self.pricelist_2) self.assertTrue(self.sale_order_6.have_coupon_code != '', 'Coupon Code: Please enter coupon code!') self.assertEqual( float_compare(self.sale_order_6.order_line[0].discount, 29.90, precision_digits=2), 0, 'Discount Line: the discount of first' 'sale order line should be 29.90!') self.assertEqual( self.sale_order_6.discount, 3969.23, 'Sale Discount: the discount for the ' 'sale order should be 3969.23!') self.assertEqual( float_compare(self.sale_order_6.order_line[1].discount, 29.90, precision_digits=2), 0, 'Discount Line: the discount of first' 'sale order line should be 29.90!') self.assertEqual( float_compare(self.sale_order_6.amount_total, 9305.78, precision_digits=2), 0, "Total not correct")
def _inter_company_create_invoice(self, dest_company): """ create an invoice for the given company : it will copy the invoice lines in the new invoice. :param dest_company : the company of the created invoice :rtype dest_company : res.company record """ self.ensure_one() # check intercompany product self._check_intercompany_product(dest_company) # if an invoice has already been genereted # delete it and force the same number inter_invoice = self.search([('auto_invoice_id', '=', self.id), ('company_id', '=', dest_company.id)]) force_number = False if inter_invoice and inter_invoice.state in ['draft', 'cancel']: force_number = inter_invoice.move_name inter_invoice.move_name = False inter_invoice.unlink() # create invoice dest_invoice_data = self._prepare_invoice_data(dest_company) if force_number: dest_invoice_data['move_name'] = force_number dest_invoice = self.create(dest_invoice_data) # create invoice lines for src_line in self.invoice_line_ids: if dest_company.use_inter_company_products and \ not src_line.product_id: raise UserError( _("The invoice line '%s' doesn't have a product. " "All invoice lines should have a product for " "inter-company invoices.") % src_line.name) dest_inv_line_data = src_line._prepare_invoice_line_data( dest_invoice, dest_company) self.env['account.invoice.line'].create(dest_inv_line_data) # add tax_line_ids in created invoice dest_invoice_line_ids = dest_invoice.invoice_line_ids if (any(line.invoice_line_tax_ids for line in dest_invoice_line_ids) and not dest_invoice.tax_line_ids): dest_invoice.compute_taxes() # Validation of account invoice precision = self.env['decimal.precision'].precision_get('Account') if (dest_company.invoice_auto_validation and not float_compare(self.amount_total, dest_invoice.amount_total, precision_digits=precision)): dest_invoice.action_invoice_open() else: # Add warning in chatter if the total amounts are different if float_compare(self.amount_total, dest_invoice.amount_total, precision_digits=precision): body = (_("WARNING!!!!! Failure in the inter-company invoice " "creation process: the total amount of this invoice " "is %s but the total amount of the invoice %s " "in the company %s is %s") % (dest_invoice.amount_total, self.number, self.company_id.name, self.amount_total)) dest_invoice.message_post(body=body) return {'dest_invoice': dest_invoice}
def _check_holidays(self): for holiday in self: if holiday.holiday_type != 'employee' or holiday.type != 'remove' or not holiday.employee_id or holiday.holiday_status_id.limit: continue leave_days = holiday.holiday_status_id.get_days(holiday.employee_id.id)[holiday.holiday_status_id.id] if float_compare(leave_days['remaining_leaves'], 0, precision_digits=2) == -1 or \ float_compare(leave_days['virtual_remaining_leaves'], 0, precision_digits=2) == -1: raise ValidationError(_('The number of remaining leaves is not sufficient for this leave type.\n' 'Please verify also the leaves waiting for validation.'))
def process(self): productions_to_do = self.env['mrp.production'] productions_not_to_do = self.env['mrp.production'] for line in self.immediate_production_line_ids: if line.to_immediate is True: productions_to_do |= line.production_id else: productions_not_to_do |= line.production_id for production in productions_to_do: error_msg = "" if production.product_tracking in ( 'lot', 'serial') and not production.lot_producing_id: production.action_generate_serial() if production.product_tracking == 'serial' and float_compare( production.qty_producing, 1, precision_rounding=production.product_uom_id.rounding ) == 1: production.qty_producing = 1 else: production.qty_producing = production.product_qty - production.qty_produced production._set_qty_producing() for move in production.move_raw_ids.filtered( lambda m: m.state not in ['done', 'cancel']): rounding = move.product_uom.rounding for move_line in move.move_line_ids: if move_line.product_uom_qty: move_line.qty_done = min( move_line.product_uom_qty, move_line.move_id.should_consume_qty) if float_compare(move.quantity_done, move.should_consume_qty, precision_rounding=rounding) >= 0: break if float_compare( move.product_uom_qty, move.quantity_done, precision_rounding=move.product_uom.rounding) == 1: if move.has_tracking in ('serial', 'lot'): error_msg += "\n - %s" % move.product_id.display_name if error_msg: error_msg = _( 'You need to supply Lot/Serial Number for products:' ) + error_msg raise UserError(error_msg) productions_to_validate = self.env.context.get( 'button_mark_done_production_ids') if productions_to_validate: productions_to_validate = self.env['mrp.production'].browse( productions_to_validate) productions_to_validate = productions_to_validate - productions_not_to_do return productions_to_validate.with_context( skip_immediate=True).button_mark_done() return True
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 create_move(self, post_move=True): created_moves = self.env['account.move'] prec = self.env['decimal.precision'].precision_get('Account') # `line.move_id` was invalidated from the cache at each iteration # To prevent to refetch `move_id` of all lines at each iteration just to check a UserError, # we use an intermediar dict which stores the information the UserError check requires. line_moves = {line: line.move_id for line in self} for line in self: if line_moves[line]: raise UserError(_('This depreciation is already linked to a journal entry! Please post or delete it.')) category_id = line.asset_id.category_id depreciation_date = self.env.context.get('depreciation_date') or line.depreciation_date or fields.Date.context_today(self) company_currency = line.asset_id.company_id.currency_id current_currency = line.asset_id.currency_id amount = current_currency.with_context(date=depreciation_date).compute(line.amount, company_currency) asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, len(line.asset_id.depreciation_line_ids)) move_line_1 = { 'name': asset_name, 'account_id': category_id.account_depreciation_id.id, 'debit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount, 'credit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0, 'journal_id': category_id.journal_id.id, 'partner_id': line.asset_id.partner_id.id, 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'sale' else False, 'currency_id': company_currency != current_currency and current_currency.id or False, 'amount_currency': company_currency != current_currency and - 1.0 * line.amount or 0.0, } move_line_2 = { 'name': asset_name, 'account_id': category_id.account_depreciation_expense_id.id, 'credit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount, 'debit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0, 'journal_id': category_id.journal_id.id, 'partner_id': line.asset_id.partner_id.id, 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'purchase' else False, 'currency_id': company_currency != current_currency and current_currency.id or False, 'amount_currency': company_currency != current_currency and line.amount or 0.0, } move_vals = { 'ref': line.asset_id.code, 'date': depreciation_date or False, 'journal_id': category_id.journal_id.id, 'asset_id': line.asset_id.id, 'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)], } move = self.env['account.move'].create(move_vals) line.write({'move_id': move.id, 'move_check': True}) line_moves[line] = move created_moves |= move if post_move and created_moves: created_moves.filtered(lambda m: any(m.asset_depreciation_ids.mapped('asset_id.category_id.open_asset'))).post() return [x.id for x in created_moves]
def _compute_qty_to_order(self): for orderpoint in self: if not orderpoint.product_id or not orderpoint.location_id: orderpoint.qty_to_order = False continue qty_to_order = 0.0 rounding = orderpoint.product_uom.rounding if float_compare(orderpoint.qty_forecast, orderpoint.product_min_qty, precision_rounding=rounding) < 0: qty_to_order = max(orderpoint.product_min_qty, orderpoint.product_max_qty) - orderpoint.qty_forecast remainder = orderpoint.qty_multiple > 0 and qty_to_order % orderpoint.qty_multiple or 0.0 if float_compare(remainder, 0.0, precision_rounding=rounding) > 0: qty_to_order += orderpoint.qty_multiple - remainder orderpoint.qty_to_order = qty_to_order
def create_move(self, post_move=True): created_moves = self.env['account.move'] for line in self: category_id = line.asset_id.category_id depreciation_date = self.env.context.get('depreciation_date') or line.depreciation_date or fields.Date.context_today(self) company_currency = line.asset_id.company_id.currency_id current_currency = line.asset_id.currency_id amount = current_currency.compute(line.amount, company_currency) sign = (category_id.journal_id.type == 'purchase' or category_id.journal_id.type == 'sale' and 1) or -1 asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, len(line.asset_id.depreciation_line_ids)) prec = self.env['decimal.precision'].precision_get('Account') move_line_1 = { 'name': asset_name, 'account_id': category_id.account_depreciation_id.id, 'debit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount, 'credit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0, 'journal_id': category_id.journal_id.id, 'partner_id': line.asset_id.partner_id.id, 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'sale' else False, 'currency_id': company_currency != current_currency and current_currency.id or False, 'amount_currency': company_currency != current_currency and - sign * line.amount or 0.0, } move_line_2 = { 'name': asset_name, 'account_id': category_id.account_depreciation_expense_id.id, 'credit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount, 'debit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0, 'journal_id': category_id.journal_id.id, 'partner_id': line.asset_id.partner_id.id, 'product_id': category_id.product_id.id if category_id.product_id else False, 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'purchase' else False, 'currency_id': company_currency != current_currency and current_currency.id or False, 'amount_currency': company_currency != current_currency and sign * line.amount or 0.0, } move_vals = { 'ref': line.asset_id.code, 'date': depreciation_date or False, 'journal_id': category_id.journal_id.id, 'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)], } move = self.env['account.move'].create(move_vals) line.write({'move_id': move.id, 'move_check': True}) created_moves |= move if post_move and created_moves: created_moves.filtered(lambda m: any(m.asset_depreciation_ids.mapped('asset_id.category_id.open_asset'))).post() return [x.id for x in created_moves]
def do_produce(self): # Nothing to do for lots since values are created using default data (stock.move.lots) quantity = self.product_qty if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0: raise UserError(_("The production order for '%s' has no quantity specified") % self.product_id.display_name) for move in self.production_id.move_raw_ids: # TODO currently not possible to guess if the user updated quantity by hand or automatically by the produce wizard. if move.product_id.tracking == 'none' and move.state not in ('done', 'cancel') and move.unit_factor: rounding = move.product_uom.rounding if self.product_id.tracking != 'none': qty_to_add = float_round(quantity * move.unit_factor, precision_rounding=rounding) move._generate_consumed_move_line(qty_to_add, self.lot_id) elif len(move._get_move_lines()) < 2: move.quantity_done += float_round(quantity * move.unit_factor, precision_rounding=rounding) else: move._set_quantity_done(quantity * move.unit_factor) for move in self.production_id.move_finished_ids: if move.product_id.tracking == 'none' and move.state not in ('done', 'cancel'): rounding = move.product_uom.rounding if move.product_id.id == self.production_id.product_id.id: move.quantity_done += float_round(quantity, precision_rounding=rounding) elif move.unit_factor: # byproducts handling move.quantity_done += float_round(quantity * move.unit_factor, precision_rounding=rounding) self.check_finished_move_lots() if self.production_id.state == 'confirmed': self.production_id.write({ 'state': 'progress', 'date_start': datetime.now(), }) return {'type': 'ir.actions.act_window_close'}
def _check_sum(self): """ Check if each cost line its valuation lines sum to the correct amount and if the overall total amount is correct also """ prec_digits = self.env['decimal.precision'].precision_get('Account') for landed_cost in self: total_amount = sum(landed_cost.valuation_adjustment_lines.mapped('additional_landed_cost')) if not tools.float_compare(total_amount, landed_cost.amount_total, precision_digits=prec_digits) == 0: return False val_to_cost_lines = defaultdict(lambda: 0.0) for val_line in landed_cost.valuation_adjustment_lines: val_to_cost_lines[val_line.cost_line_id] += val_line.additional_landed_cost if any(tools.float_compare(cost_line.price_unit, val_amount, precision_digits=prec_digits) != 0 for cost_line, val_amount in val_to_cost_lines.items()): return False return True
def _confirm_invoice(self): """ Check tx state, confirm and pay potential invoice """ self.ensure_one() # check tx state, confirm the potential SO if self.account_invoice_id.state != 'open': _logger.warning('<%s> transaction STATE INCORRECT for invoice %s (ID %s, state %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id, self.account_invoice_id.state) return 'pay_invoice_invalid_doc_state' if not float_compare(self.amount, self.account_invoice_id.amount_total, 2) == 0: _logger.warning( '<%s> transaction AMOUNT MISMATCH for invoice %s (ID %s): expected %r, got %r', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id, self.account_invoice_id.amount_total, self.amount, ) self.account_invoice_id.message_post( subject=_("Amount Mismatch (%s)") % self.acquirer_id.provider, body=_("The invoice was not confirmed despite response from the acquirer (%s): invoice amount is %r but acquirer replied with %r.") % ( self.acquirer_id.provider, self.account_invoice_id.amount_total, self.amount, ) ) return 'pay_invoice_tx_amount' if self.state == 'authorized' and self.acquirer_id.capture_manually: _logger.info('<%s> transaction authorized, nothing to do with invoice %s (ID %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id) elif self.state == 'done': _logger.info('<%s> transaction completed, paying invoice %s (ID %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id) self._pay_invoice() else: _logger.warning('<%s> transaction MISMATCH for invoice %s (ID %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id) return 'pay_invoice_tx_state' return True
def _select_seller(self, partner_id=False, quantity=0.0, date=None, uom_id=False, params=False): self.ensure_one() if date is None: date = fields.Date.context_today(self) precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') res = self.env['product.supplierinfo'] sellers = self._prepare_sellers(params) sellers = sellers.filtered(lambda s: not s.company_id or s.company_id.id == self.env.company.id) for seller in sellers: # Set quantity in UoM of seller quantity_uom_seller = quantity if quantity_uom_seller and uom_id and uom_id != seller.product_uom: quantity_uom_seller = uom_id._compute_quantity(quantity_uom_seller, seller.product_uom) if seller.date_start and seller.date_start > date: continue if seller.date_end and seller.date_end < date: continue if partner_id and seller.name not in [partner_id, partner_id.parent_id]: continue if quantity is not None and float_compare(quantity_uom_seller, seller.min_qty, precision_digits=precision) == -1: continue if seller.product_id and seller.product_id != self: continue if not res or res.name == seller.name: res |= seller return res.sorted('price')[:1]
def action_validate(self): self.ensure_one() precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') available_qty = self.env['stock.quant']._get_available_quantity( self.product_id, self.location_id, self.lot_id, strict=True) if float_compare(available_qty, self.product_qty, precision_digits=precision) >= 0: return self.action_unbuild() else: return { 'name': _('Insufficient Quantity'), 'view_type': 'form', 'view_mode': 'form', 'res_model': 'stock.warn.insufficient.qty.unbuild', 'view_id': self.env.ref( 'mrp.stock_warn_insufficient_qty_unbuild_form_view').id, 'type': 'ir.actions.act_window', 'context': { 'default_product_id': self.product_id.id, 'default_location_id': self.location_id.id, 'default_unbuild_id': self.id }, 'target': 'new' }
def _onchange_quantity_context(self): if self.product_id: self.product_uom_id = self.product_id.uom_id if self.product_id and self.location_id and self.product_id.uom_id.category_id == self.product_uom_id.category_id: # TDE FIXME: last part added because crash theoretical_qty = self.product_id.get_theoretical_quantity( self.product_id.id, self.location_id.id, lot_id=self.prod_lot_id.id, package_id=self.package_id.id, owner_id=self.partner_id.id, to_uom=self.product_uom_id.id, ) else: theoretical_qty = 0 # Sanity check on the lot. if self.prod_lot_id: if self.product_id.tracking == 'none' or self.product_id != self.prod_lot_id.product_id: self.prod_lot_id = False if self.prod_lot_id and self.product_id.tracking == 'serial': # We force `product_qty` to 1 for SN tracked product because it's # the only relevant value aside 0 for this kind of product. self.product_qty = 1 elif self.product_id and float_compare(self.product_qty, self.theoretical_qty, precision_rounding=self.product_uom_id.rounding) == 0: # We update `product_qty` only if it equals to `theoretical_qty` to # avoid to reset quantity when user manually set it. self.product_qty = theoretical_qty self.theoretical_qty = theoretical_qty
def action_validate(self): if not self.exists(): return self.ensure_one() if not self.user_has_groups('stock.group_stock_manager'): raise UserError(_("Only a stock manager can validate an inventory adjustment.")) if self.state != 'confirm': raise UserError(_( "You can't validate the inventory '%s', maybe this inventory " "has been already validated or isn't ready.", self.name)) inventory_lines = self.line_ids.filtered(lambda l: l.product_id.tracking in ['lot', 'serial'] and not l.prod_lot_id and l.theoretical_qty != l.product_qty) lines = self.line_ids.filtered(lambda l: float_compare(l.product_qty, 1, precision_rounding=l.product_uom_id.rounding) > 0 and l.product_id.tracking == 'serial' and l.prod_lot_id) if inventory_lines and not lines: wiz_lines = [(0, 0, {'product_id': product.id, 'tracking': product.tracking}) for product in inventory_lines.mapped('product_id')] wiz = self.env['stock.track.confirmation'].create({'inventory_id': self.id, 'tracking_line_ids': wiz_lines}) return { 'name': _('Tracked Products in Inventory Adjustment'), 'type': 'ir.actions.act_window', 'view_mode': 'form', 'views': [(False, 'form')], 'res_model': 'stock.track.confirmation', 'target': 'new', 'res_id': wiz.id, } self._action_done() self.line_ids._check_company() self._check_company() return True
def _select_seller(self, partner_id=False, quantity=0.0, date=None, uom_id=False): self.ensure_one() if date is None: date = fields.Date.context_today(self) precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') res = self.env['product.supplierinfo'] for seller in self.seller_ids: # Set quantity in UoM of seller quantity_uom_seller = quantity if quantity_uom_seller and uom_id and uom_id != seller.product_uom: quantity_uom_seller = uom_id._compute_quantity(quantity_uom_seller, seller.product_uom) if seller.date_start and seller.date_start > date: continue if seller.date_end and seller.date_end < date: continue if partner_id and seller.name not in [partner_id, partner_id.parent_id]: continue if float_compare(quantity_uom_seller, seller.min_qty, precision_digits=precision) == -1: continue if seller.product_id and seller.product_id != self: continue res |= seller break return res
def action_validate(self): self.ensure_one() if self.product_id.type != 'product': return self.do_scrap() precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') available_qty = sum(self.env['stock.quant']._gather(self.product_id, self.location_id, self.lot_id, self.package_id, self.owner_id, strict=True).mapped('quantity')) scrap_qty = self.product_uom_id._compute_quantity(self.scrap_qty, self.product_id.uom_id) if float_compare(available_qty, scrap_qty, precision_digits=precision) >= 0: return self.do_scrap() else: return { 'name': _('Insufficient Quantity'), 'view_type': 'form', 'view_mode': 'form', 'res_model': 'stock.warn.insufficient.qty.scrap', 'view_id': self.env.ref('stock.stock_warn_insufficient_qty_scrap_form_view').id, 'type': 'ir.actions.act_window', 'context': { 'default_product_id': self.product_id.id, 'default_location_id': self.location_id.id, 'default_scrap_id': self.id }, 'target': 'new' }
def _compute_consumed_less_than_planned(self): for order in self: order.consumed_less_than_planned = any( order.move_raw_ids.filtered(lambda move: float_compare( move.quantity_done, move.product_uom_qty, precision_rounding=move.product_uom.rounding) == -1))
def _check_package(self): default_uom = self.product_id.uom_id pack = self.product_packaging qty = self.product_uom_qty q = default_uom._compute_quantity(pack.qty, self.product_uom) # We do not use the modulo operator to check if qty is a mltiple of q. Indeed the quantity # per package might be a float, leading to incorrect results. For example: # 8 % 1.6 = 1.5999999999999996 # 5.4 % 1.8 = 2.220446049250313e-16 if (qty and q and float_compare(qty / q, float_round(qty / q, precision_rounding=1.0), precision_rounding=0.001) != 0): newqty = qty - (qty % q) + q return { 'warning': { 'title': _('Warning'), 'message': _("This product is packaged by %(pack_size).2f %(pack_name)s. You should sell %(quantity).2f %(unit)s.", pack_size=pack.qty, pack_name=default_uom.name, quantity=newqty, unit=self.product_uom.name), }, } return {}
def action_validate(self): self.ensure_one() if self.filtered(lambda repair: any(op.product_uom_qty < 0 for op in repair.operations)): raise UserError(_("You can not enter negative quantities.")) if self.product_id.type == 'consu': return self.action_repair_confirm() precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') available_qty_owner = self.env['stock.quant']._get_available_quantity(self.product_id, self.location_id, self.lot_id, owner_id=self.partner_id, strict=True) available_qty_noown = self.env['stock.quant']._get_available_quantity(self.product_id, self.location_id, self.lot_id, strict=True) repair_qty = self.product_uom._compute_quantity(self.product_qty, self.product_id.uom_id) for available_qty in [available_qty_owner, available_qty_noown]: if float_compare(available_qty, repair_qty, precision_digits=precision) >= 0: return self.action_repair_confirm() else: return { 'name': self.product_id.display_name + _(': Insufficient Quantity To Repair'), 'view_mode': 'form', 'res_model': 'stock.warn.insufficient.qty.repair', 'view_id': self.env.ref('repair.stock_warn_insufficient_qty_repair_form_view').id, 'type': 'ir.actions.act_window', 'context': { 'default_product_id': self.product_id.id, 'default_location_id': self.location_id.id, 'default_repair_id': self.id, 'default_quantity': repair_qty, 'default_product_uom_name': self.product_id.uom_name }, 'target': 'new' }
def _compute_invoice_status(self): """ Compute the invoice status of a SO line. Possible statuses: - no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to invoice. This is also hte default value if the conditions of no other status is met. - to invoice: we refer to the quantity to invoice of the line. Refer to method `_get_to_invoice_qty()` for more information on how this quantity is calculated. - upselling: this is possible only for a product invoiced on ordered quantities for which we delivered more than expected. The could arise if, for example, a project took more time than expected but we decided not to invoice the extra cost to the client. This occurs onyl in state 'sale', so that when a SO is set to done, the upselling opportunity is removed from the list. - invoiced: the quantity invoiced is larger or equal to the quantity ordered. """ precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') for line in self: if not line.order_id.invoice_policy: if line.state not in ('sale', 'done'): line.invoice_status = 'no' elif not float_is_zero(line.qty_to_invoice, precision_digits=precision): line.invoice_status = 'to invoice' elif line.state == 'sale' and line.product_id.invoice_policy == 'order' and\ float_compare(line.qty_delivered, line.product_uom_qty, precision_digits=precision) == 1: line.invoice_status = 'upselling' elif float_compare(line.qty_invoiced, line.product_uom_qty, precision_digits=precision) >= 0: line.invoice_status = 'invoiced' else: line.invoice_status = 'no' else: if line.state not in ('sale', 'done'): line.invoice_status = 'no' elif not float_is_zero(line.qty_to_invoice, precision_digits=precision): line.invoice_status = 'to invoice' elif line.state == 'sale' and line.order_id.invoice_policy == 'order' and\ float_compare(line.qty_delivered, line.product_uom_qty, precision_digits=precision) == 1: line.invoice_status = 'upselling' elif float_compare(line.qty_invoiced, line.product_uom_qty, precision_digits=precision) >= 0: line.invoice_status = 'invoiced' else: line.invoice_status = 'no'
def _action_launch_procurement_rule(self): """ Launch procurement group run method with required/custom fields genrated by a sale order line. procurement group will launch '_run_move', '_run_buy' or '_run_manufacture' depending on the sale order line product rule. """ precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') errors = [] for line in self: if line.state != 'sale' or not line.product_id.type in ('consu', 'product'): continue qty = 0.0 for move in line.move_ids.filtered(lambda r: r.state != 'cancel'): qty += move.product_qty if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0: continue group_id = line.order_id.procurement_group_id if not group_id: group_id = self.env['procurement.group'].create({ 'name': line.order_id.name, 'move_type': line.order_id.picking_policy, 'sale_id': line.order_id.id, 'partner_id': line.order_id.partner_shipping_id.id, }) line.order_id.procurement_group_id = group_id else: # In case the procurement group is already created and the order was # cancelled, we need to update certain values of the group. updated_vals = {} if group_id.partner_id != line.order_id.partner_shipping_id: updated_vals.update( {'partner_id': line.order_id.partner_shipping_id.id}) if group_id.move_type != line.order_id.picking_policy: updated_vals.update( {'move_type': line.order_id.picking_policy}) if updated_vals: group_id.write(updated_vals) values = line._prepare_procurement_values(group_id=group_id) product_qty = line.product_uom_qty - qty try: self.env['procurement.group'].run( line.product_id, product_qty, line.product_uom, line.order_id.partner_shipping_id.property_stock_customer, line.name, line.order_id.name, values) except UserError as error: errors.append(error.name) if errors: raise UserError('\n'.join(errors)) return True
def _confirm_so(self): """ Check tx state, confirm the potential SO """ self.ensure_one() if self.sale_order_id.state not in ['draft', 'sent', 'sale']: _logger.warning( '<%s> transaction STATE INCORRECT for order %s (ID %s, state %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id, self.sale_order_id.state) return 'pay_sale_invalid_doc_state' if not float_compare(self.amount, self.sale_order_id.amount_total, 2) == 0: _logger.warning( '<%s> transaction AMOUNT MISMATCH for order %s (ID %s): expected %r, got %r', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id, self.sale_order_id.amount_total, self.amount, ) self.sale_order_id.message_post( subject=_("Amount Mismatch (%s)") % self.acquirer_id.provider, body= _("The sale order was not confirmed despite response from the acquirer (%s): SO amount is %r but acquirer replied with %r." ) % ( self.acquirer_id.provider, self.sale_order_id.amount_total, self.amount, )) return 'pay_sale_tx_amount' if self.state == 'authorized' and self.acquirer_id.capture_manually: _logger.info( '<%s> transaction authorized, auto-confirming order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) if self.sale_order_id.state in ('draft', 'sent'): self.sale_order_id.with_context( send_email=True).action_confirm() elif self.state == 'done': _logger.info( '<%s> transaction completed, auto-confirming order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) if self.sale_order_id.state in ('draft', 'sent'): self.sale_order_id.with_context( send_email=True).action_confirm() elif self.state not in ['cancel', 'error' ] and self.sale_order_id.state == 'draft': _logger.info( '<%s> transaction pending/to confirm manually, sending quote email for order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) self.sale_order_id.force_quotation_send() else: _logger.warning('<%s> transaction MISMATCH for order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) return 'pay_sale_tx_state' return True
def _compute_is_produced(self): self.is_produced = False for order in self.filtered( lambda p: p.production_id and p.production_id.product_uom_id): rounding = order.production_id.product_uom_id.rounding order.is_produced = float_compare(order.qty_produced, order.production_id.product_qty, precision_rounding=rounding) >= 0
def _reservation_is_updatable(self, quantity, reserved_quant): self.ensure_one() if self.produce_line_ids.lot_id: ml_remaining_qty = self.qty_done - self.product_uom_qty ml_remaining_qty = self.product_uom_id._compute_quantity(ml_remaining_qty, self.product_id.uom_id, rounding_method="HALF-UP") if float_compare(ml_remaining_qty, quantity, precision_rounding=self.product_id.uom_id.rounding) < 0: return False return super(StockMoveLine, self)._reservation_is_updatable(quantity, reserved_quant)
def _run_pull(self, procurements): moves_values_by_company = defaultdict(list) mtso_products_by_locations = defaultdict(list) # To handle the `mts_else_mto` procure method, we do a preliminary loop to # isolate the products we would need to read the forecasted quantity, # in order to to batch the read. We also make a sanitary check on the # `location_src_id` field. for procurement, rule in procurements: if not rule.location_src_id: msg = _('No source location defined on stock rule: %s!') % ( rule.name, ) raise ProcurementException([(procurement, msg)]) if rule.procure_method == 'mts_else_mto': mtso_products_by_locations[rule.location_src_id].append( procurement.product_id.id) # Get the forecasted quantity for the `mts_else_mto` procurement. forecasted_qties_by_loc = {} for location, product_ids in mtso_products_by_locations.items(): products = self.env['product.product'].browse( product_ids).with_context(location=location.id) forecasted_qties_by_loc[location] = { product.id: product.free_qty for product in products } # Prepare the move values, adapt the `procure_method` if needed. for procurement, rule in procurements: procure_method = rule.procure_method if rule.procure_method == 'mts_else_mto': qty_needed = procurement.product_uom._compute_quantity( procurement.product_qty, procurement.product_id.uom_id) qty_available = forecasted_qties_by_loc[rule.location_src_id][ procurement.product_id.id] if float_compare(qty_needed, qty_available, precision_rounding=procurement.product_id. uom_id.rounding) <= 0: procure_method = 'make_to_stock' forecasted_qties_by_loc[rule.location_src_id][ procurement.product_id.id] -= qty_needed else: procure_method = 'make_to_order' move_values = rule._get_stock_move_values(*procurement) move_values['procure_method'] = procure_method moves_values_by_company[procurement.company_id.id].append( move_values) for company_id, moves_values in moves_values_by_company.items(): # create the move as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example) moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo( ).with_company(company_id).create(moves_values) # Since action_confirm launch following procurement_group we should activate it. moves._action_confirm() return True
def test_pls_no_share_stage(self): """ We test here the situation where all stages are team specific, as there is a current limitation (can be seen in _pls_get_won_lost_total_count) regarding the first stage (used to know how many lost and won there is) that requires to have no team assigned to it.""" Lead = self.env['crm.lead'] team_id = self.env['crm.team'].create([{'name': 'Team Test'}]).id self.env['crm.stage'].search([('team_id', '=', False) ]).write({'team_id': team_id}) lead = Lead.create({ 'name': 'team', 'team_id': team_id, 'probability': 41.23 }) Lead._cron_update_automated_probabilities() self.assertEqual(tools.float_compare(lead.probability, 41.23, 2), 0) self.assertEqual(tools.float_compare(lead.automated_probability, 0, 2), 0)
def _check_main_currency_rounding(self): if any(precision.name == 'Account' and tools.float_compare( self.env.user.company_id.currency_id.rounding, 10**-precision.digits, precision_digits=6) == -1 for precision in self): raise ValidationError( _("You cannot define the decimal precision of 'Account' as greater than the rounding factor of the company's main currency" )) return True
def reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False): res = super(AccountMoveLine, self).reconcile(writeoff_acc_id=writeoff_acc_id, writeoff_journal_id=writeoff_journal_id) account_move_ids = [l.move_id.id for l in self if float_compare(l.move_id.matched_percentage, 1, precision_digits=5) == 0] if account_move_ids: expense_sheets = self.env['hr.expense.sheet'].search([ ('account_move_id', 'in', account_move_ids), ('state', '!=', 'done') ]) expense_sheets.set_to_paid() return res