def _compute_quantities_dict(self, lot_id, owner_id, package_id, from_date=False, to_date=False): domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self._get_domain_locations() domain_quant = [('product_id', 'in', self.ids)] + domain_quant_loc dates_in_the_past = False # only to_date as to_date will correspond to qty_available to_date = fields.Datetime.to_datetime(to_date) if to_date and to_date < fields.Datetime.now(): dates_in_the_past = True domain_move_in = [('product_id', 'in', self.ids)] + domain_move_in_loc domain_move_out = [('product_id', 'in', self.ids)] + domain_move_out_loc if lot_id is not None: domain_quant += [('lot_id', '=', lot_id)] if owner_id is not None: domain_quant += [('owner_id', '=', owner_id)] domain_move_in += [('restrict_partner_id', '=', owner_id)] domain_move_out += [('restrict_partner_id', '=', owner_id)] if package_id is not None: domain_quant += [('package_id', '=', package_id)] if dates_in_the_past: domain_move_in_done = list(domain_move_in) domain_move_out_done = list(domain_move_out) if from_date: domain_move_in += [('date', '>=', from_date)] domain_move_out += [('date', '>=', from_date)] if to_date: domain_move_in += [('date', '<=', to_date)] domain_move_out += [('date', '<=', to_date)] Move = self.env['stock.move'] Quant = self.env['stock.quant'] domain_move_in_todo = [('state', 'in', ('waiting', 'confirmed', 'assigned', 'partially_available'))] + domain_move_in domain_move_out_todo = [('state', 'in', ('waiting', 'confirmed', 'assigned', 'partially_available'))] + domain_move_out moves_in_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id')) moves_out_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id')) quants_res = dict((item['product_id'][0], item['quantity']) for item in Quant.read_group(domain_quant, ['product_id', 'quantity'], ['product_id'], orderby='id')) if dates_in_the_past: # Calculate the moves that were done before now to calculate back in time (as most questions will be recent ones) domain_move_in_done = [('state', '=', 'done'), ('date', '>', to_date)] + domain_move_in_done domain_move_out_done = [('state', '=', 'done'), ('date', '>', to_date)] + domain_move_out_done moves_in_res_past = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_done, ['product_id', 'product_qty'], ['product_id'], orderby='id')) moves_out_res_past = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_done, ['product_id', 'product_qty'], ['product_id'], orderby='id')) res = dict() for product in self.with_context(prefetch_fields=False): product_id = product.id rounding = product.uom_id.rounding res[product_id] = {} if dates_in_the_past: qty_available = quants_res.get(product_id, 0.0) - moves_in_res_past.get(product_id, 0.0) + moves_out_res_past.get(product_id, 0.0) else: qty_available = quants_res.get(product_id, 0.0) res[product_id]['qty_available'] = float_round(qty_available, precision_rounding=rounding) res[product_id]['incoming_qty'] = float_round(moves_in_res.get(product_id, 0.0), precision_rounding=rounding) res[product_id]['outgoing_qty'] = float_round(moves_out_res.get(product_id, 0.0), precision_rounding=rounding) res[product_id]['virtual_available'] = float_round( qty_available + res[product_id]['incoming_qty'] - res[product_id]['outgoing_qty'], precision_rounding=rounding) return res
def _compute_duration_display(self): for allocation in self: allocation.duration_display = '%g %s' % ( (float_round(allocation.number_of_hours_display, precision_digits=2) if allocation.type_request_unit == 'hour' else float_round(allocation.number_of_days_display, precision_digits=2)), _('hours') if allocation.type_request_unit == 'hour' else _('days'))
def trans_rec_get(self): context = self._context or {} credit = debit = 0 lines = self.env['account.move.line'].browse(context.get('active_ids', [])) for line in lines: if not line.reconciled: credit += line.credit debit += line.debit precision = self.env.user.company_id.currency_id.decimal_places writeoff = float_round(debit - credit, precision_digits=precision) credit = float_round(credit, precision_digits=precision) debit = float_round(debit, precision_digits=precision) return {'trans_nbr': len(lines), 'credit': credit, 'debit': debit, 'writeoff': writeoff}
def _quant_split(self, qty): self.ensure_one() rounding = self.product_id.uom_id.rounding if float_compare(abs(self.qty), abs(qty), precision_rounding=rounding) <= 0: # if quant <= qty in abs, take it entirely return False qty_round = float_round(qty, precision_rounding=rounding) new_qty_round = float_round(self.qty - qty, precision_rounding=rounding) # Fetch the history_ids manually as it will not do a join with the stock moves then (=> a lot faster) self._cr.execute("""SELECT move_id FROM stock_quant_move_rel WHERE quant_id = %s""", (self.id,)) res = self._cr.fetchall() new_quant = self.sudo().copy(default={'qty': new_qty_round, 'history_ids': [(4, x[0]) for x in res]}) self.sudo().write({'qty': qty_round}) return new_quant
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, ): """Create a quant in the destination location and create a negative quant in the source location if it's an internal location. """ price_unit = move.get_price_unit() location = force_location_to or move.location_dest_id rounding = move.product_id.uom_id.rounding vals = { "product_id": move.product_id.id, "location_id": location.id, "qty": float_round(qty, precision_rounding=rounding), "cost": price_unit, "history_ids": [(4, move.id)], "in_date": datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT), "company_id": move.company_id.id, "lot_id": lot_id, "owner_id": owner_id, "package_id": dest_package_id, } if move.location_id.usage == "internal": # if we were trying to move something from an internal location and reach here (quant creation), # it means that a negative quant has to be created as well. negative_vals = vals.copy() negative_vals["location_id"] = force_location_from and force_location_from.id or move.location_id.id negative_vals["qty"] = float_round(-qty, precision_rounding=rounding) negative_vals["cost"] = price_unit negative_vals["negative_move_id"] = move.id negative_vals["package_id"] = src_package_id negative_quant_id = self.sudo().create(negative_vals) vals.update({"propagated_from_id": negative_quant_id.id}) picking_type = move.picking_id and move.picking_id.picking_type_id or False if ( lot_id and move.product_id.tracking == "serial" and (not picking_type or (picking_type.use_create_lots or picking_type.use_existing_lots)) ): if qty != 1.0: raise UserError(_("You should only receive by the piece with the same serial number")) # create the quant as superuser, because we want to restrict the creation of quant manually: we should always use this method to create quants return self.sudo().create(vals)
def get_opening_move_differences(self, opening_move_lines): currency = self.currency_id balancing_move_line = opening_move_lines.filtered(lambda x: x.account_id == self.get_unaffected_earnings_account()) debits_sum = credits_sum = 0.0 for line in opening_move_lines: if line != balancing_move_line: #skip the autobalancing move line debits_sum += line.debit credits_sum += line.credit difference = abs(debits_sum - credits_sum) debit_diff = (debits_sum > credits_sum) and float_round(difference, precision_rounding=currency.rounding) or 0.0 credit_diff = (debits_sum < credits_sum) and float_round(difference, precision_rounding=currency.rounding) or 0.0 return debit_diff, credit_diff
def _put_in_pack(self): package = False for pick in self: operations = pick.move_line_ids.filtered(lambda o: o.qty_done > 0 and not o.result_package_id) operation_ids = self.env['stock.move.line'] if operations: package = self.env['stock.quant.package'].create({}) for operation in operations: if float_compare(operation.qty_done, operation.product_uom_qty, precision_rounding=operation.product_uom_id.rounding) >= 0: operation_ids |= operation else: quantity_left_todo = float_round( operation.product_uom_qty - operation.qty_done, precision_rounding=operation.product_uom_id.rounding, rounding_method='UP') done_to_keep = operation.qty_done new_operation = operation.copy( default={'product_uom_qty': 0, 'qty_done': operation.qty_done}) operation.write({'product_uom_qty': quantity_left_todo, 'qty_done': 0.0}) new_operation.write({'product_uom_qty': done_to_keep}) operation_ids |= new_operation operation_ids.write({'result_package_id': package.id}) else: raise UserError(_('Please process some quantities to put in the pack first!')) return package
def _inverse_remaining_leaves(self): status_list = self.env['hr.leave.type'].search([('limit', '=', False)]) # Create leaves (adding remaining leaves) or raise (reducing remaining leaves) actual_remaining = self._get_remaining_leaves() for employee in self.filtered(lambda employee: employee.remaining_leaves): # check the status list. This is done here and not before the loop to avoid raising # exception on employee creation (since we are in a computed field). if len(status_list) != 1: raise UserError(_("The feature behind the field 'Remaining Legal Leaves' can only be used when there is only one " "leave type with the option 'Allow to Override Limit' unchecked. (%s Found). " "Otherwise, the update is ambiguous as we cannot decide on which leave type the update has to be done. " "\n You may prefer to use the classic menus 'Leave Requests' and 'Allocation Requests' located in Leaves Application " "to manage the leave days of the employees if the configuration does not allow to use this field.") % (len(status_list))) status = status_list[0] if status_list else None if not status: continue # if a status is found, then compute remaing leave for current employee difference = float_round(employee.remaining_leaves - actual_remaining.get(employee.id, 0), precision_digits=2) if difference > 0: leave = self.env['hr.leave.allocation'].create({ 'name': _('Allocation for %s') % employee.name, 'employee_id': employee.id, 'holiday_status_id': status.id, 'holiday_type': 'employee', 'number_of_days_temp': difference }) leave.action_approve() if leave.validation_type == 'both': leave.action_validate() elif difference < 0: raise UserError(_('You cannot reduce validated allocation requests.'))
def _onchange_hours_per_day(self): attendances = self._get_global_attendances() hour_count = 0.0 for attendance in attendances: hour_count += attendance.hour_to - attendance.hour_from if attendances: self.hours_per_day = float_round(hour_count / float(len(set(attendances.mapped('dayofweek')))), precision_digits=2)
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) param_plus = { 'return_url': ogone_tx_values.pop('return_url', False) } 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': url_encode(param_plus), } 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 _compute_leaves_count(self): all_leaves = self.env['hr.leave.report'].read_group([ ('employee_id', 'in', self.ids), ('state', '=', 'validate') ], fields=['number_of_days', 'employee_id'], groupby=['employee_id']) mapping = dict([(leave['employee_id'][0], leave['number_of_days']) for leave in all_leaves]) for employee in self: employee.leaves_count = float_round(mapping.get(employee.id, 0), precision_digits=2)
def name_get(self): if not self._context.get('employee_id'): # leave counts is based on employee_id, would be inaccurate if not based on correct employee return super(HolidaysType, self).name_get() res = [] for record in self: name = record.name if record.allocation_type != 'no': name = "%(name)s (%(count)s)" % { 'name': name, 'count': _('%g remaining out of %g') % ( float_round(record.virtual_remaining_leaves, precision_digits=2) or 0.0, float_round(record.max_leaves, precision_digits=2) or 0.0, ) } res.append((record.id, name)) return res
def _compute_mrp_product_qty(self): date_from = fields.Datetime.to_string(fields.datetime.now() - timedelta(days=365)) #TODO: state = done? domain = [('state', '=', 'done'), ('product_id', 'in', self.ids), ('date_planned_start', '>', date_from)] read_group_res = self.env['mrp.production'].read_group(domain, ['product_id', 'product_uom_qty'], ['product_id']) mapped_data = dict([(data['product_id'][0], data['product_uom_qty']) for data in read_group_res]) for product in self: product.mrp_product_qty = float_round(mapped_data.get(product.id, 0), precision_rounding=product.uom_id.rounding)
def _free_reservation(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, ml_to_ignore=None): """ When editing a done move line or validating one with some forced quantities, it is possible to impact quants that were not reserved. It is therefore necessary to edit or unlink the move lines that reserved a quantity now unavailable. :param ml_to_ignore: recordset of `stock.move.line` that should NOT be unreserved """ self.ensure_one() if ml_to_ignore is None: ml_to_ignore = self.env['stock.move.line'] ml_to_ignore |= self # Check the available quantity, with the `strict` kw set to `True`. If the available # quantity is greather than the quantity now unavailable, there is nothing to do. available_quantity = self.env['stock.quant']._get_available_quantity( product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True ) if quantity > available_quantity: # We now have to find the move lines that reserved our now unavailable quantity. We # take care to exclude ourselves and the move lines were work had already been done. oudated_move_lines_domain = [ ('move_id.state', 'not in', ['done', 'cancel']), ('product_id', '=', product_id.id), ('lot_id', '=', lot_id.id if lot_id else False), ('location_id', '=', location_id.id), ('owner_id', '=', owner_id.id if owner_id else False), ('package_id', '=', package_id.id if package_id else False), ('product_qty', '>', 0.0), ('id', 'not in', ml_to_ignore.ids), ] oudated_candidates = self.env['stock.move.line'].search(oudated_move_lines_domain) # As the move's state is not computed over the move lines, we'll have to manually # recompute the moves which we adapted their lines. move_to_recompute_state = self.env['stock.move'] rounding = self.product_uom_id.rounding for candidate in oudated_candidates: if float_compare(candidate.product_qty, quantity, precision_rounding=rounding) <= 0: quantity -= candidate.product_qty move_to_recompute_state |= candidate.move_id if candidate.qty_done: candidate.product_uom_qty = 0.0 else: candidate.unlink() else: # split this move line and assign the new part to our extra move quantity_split = float_round( candidate.product_qty - quantity, precision_rounding=self.product_uom_id.rounding, rounding_method='UP') candidate.product_uom_qty = self.product_id.uom_id._compute_quantity(quantity_split, candidate.product_uom_id, rounding_method='HALF-UP') quantity -= quantity_split move_to_recompute_state |= candidate.move_id if quantity == 0.0: break move_to_recompute_state._recompute_state()
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): '''Create a quant in the destination location and create a negative quant in the source location if it's an internal location. ''' price_unit = move.get_price_unit() location = force_location_to or move.location_dest_id rounding = move.product_id.uom_id.rounding vals = { 'product_id': move.product_id.id, 'location_id': location.id, 'qty': float_round(qty, precision_rounding=rounding), 'cost': price_unit, 'history_ids': [(4, move.id)], 'in_date': datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'company_id': move.company_id.id, 'lot_id': lot_id, 'owner_id': owner_id, 'package_id': dest_package_id, } if move.location_id.usage == 'internal': # if we were trying to move something from an internal location and reach here (quant creation), # it means that a negative quant has to be created as well. negative_vals = vals.copy() negative_vals['location_id'] = force_location_from and force_location_from.id or move.location_id.id negative_vals['qty'] = float_round(-qty, precision_rounding=rounding) negative_vals['cost'] = price_unit negative_vals['negative_move_id'] = move.id negative_vals['package_id'] = src_package_id negative_quant_id = self.sudo().create(negative_vals) vals.update({'propagated_from_id': negative_quant_id.id}) # In case of serial tracking, check if the product does not exist somewhere internally already picking_type = move.picking_id and move.picking_id.picking_type_id or False if lot_id and move.product_id.tracking == 'serial' and (not picking_type or (picking_type.use_create_lots or picking_type.use_existing_lots)): if qty != 1.0: raise UserError(_('You should only receive by the piece with the same serial number')) other_quants = self.search([('product_id', '=', move.product_id.id), ('lot_id', '=', lot_id), ('qty', '>', 0.0), ('location_id.usage', '=', 'internal')]) if other_quants: lot_name = self.env['stock.production.lot'].browse(lot_id).name raise UserError(_('The serial number %s is already in stock.') % lot_name + _("Otherwise make sure the right stock/owner is set.")) # create the quant as superuser, because we want to restrict the creation of quant manually: we should always use this method to create quants return self.sudo().create(vals)
def _compute_timesheet_revenue(self): for invoice in self: for invoice_line in invoice.invoice_line_ids.filtered(lambda line: line.product_id.type == 'service'): uninvoiced_timesheet_lines = self.env['account.analytic.line'].sudo().search([ ('so_line', 'in', invoice_line.sale_line_ids.ids), ('project_id', '!=', False), ('timesheet_invoice_id', '=', False), ('timesheet_invoice_type', 'in', ['billable_time', 'billable_fixed']) ]).with_context(create=True) # context key required to avoid loop # NOTE JEM : changing quantity (or unit price) of invoice line does not impact the revenue calculation. (FP specs) if uninvoiced_timesheet_lines: precision = invoice_line.currency_id.decimal_places # delivered : update revenue with the prorata of number of hours on the timesheet line if invoice_line.product_id.invoice_policy == 'delivery': invoiced_price_per_hour = float_round(invoice_line.price_subtotal / float(sum(uninvoiced_timesheet_lines.mapped('unit_amount'))), precision) # invoicing analytic lines of different currency total_revenue_per_currency = dict.fromkeys(uninvoiced_timesheet_lines.mapped('company_currency_id').ids, 0.0) for index, timesheet_line in enumerate(uninvoiced_timesheet_lines): if index+1 != len(uninvoiced_timesheet_lines): line_revenue = invoice_line.currency_id.compute(invoiced_price_per_hour, timesheet_line.company_currency_id) * timesheet_line.unit_amount total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue else: # last line: add the difference to avoid rounding problem total_revenue = sum([self.env['res.currency'].browse(currency_id).compute(amount, timesheet_line.company_currency_id) for currency_id, amount in total_revenue_per_currency.items()]) line_revenue = invoice_line.currency_id.compute(invoice_line.price_subtotal, timesheet_line.company_currency_id) - total_revenue timesheet_line.write({ 'timesheet_invoice_id': invoice.id, 'timesheet_revenue': timesheet_line.company_currency_id.round(line_revenue), }) # ordered : update revenue with the prorata of theorical revenue elif invoice_line.product_id.invoice_policy == 'order': zero_timesheet_revenue = uninvoiced_timesheet_lines.filtered(lambda line: line.timesheet_revenue == 0.0) no_zero_timesheet_revenue = uninvoiced_timesheet_lines.filtered(lambda line: line.timesheet_revenue != 0.0) # timesheet with zero theorical revenue keep the same revenue, but become invoiced (invoice_id set) zero_timesheet_revenue.write({'timesheet_invoice_id': invoice.id}) # invoicing analytic lines of different currency total_revenue_per_currency = dict.fromkeys(no_zero_timesheet_revenue.mapped('company_currency_id').ids, 0.0) for index, timesheet_line in enumerate(no_zero_timesheet_revenue): if index+1 != len(no_zero_timesheet_revenue): price_subtotal_inv = invoice_line.currency_id.compute(invoice_line.price_subtotal, timesheet_line.company_currency_id) price_subtotal_sol = timesheet_line.so_line.currency_id.compute(timesheet_line.so_line.price_subtotal, timesheet_line.company_currency_id) line_revenue = timesheet_line.timesheet_revenue * price_subtotal_inv / price_subtotal_sol total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue else: # last line: add the difference to avoid rounding problem last_price_subtotal_inv = invoice_line.currency_id.compute(invoice_line.price_subtotal, timesheet_line.company_currency_id) total_revenue = sum([self.env['res.currency'].browse(currency_id).compute(amount, timesheet_line.company_currency_id) for currency_id, amount in total_revenue_per_currency.items()]) line_revenue = last_price_subtotal_inv - total_revenue timesheet_line.write({ 'timesheet_invoice_id': invoice.id, 'timesheet_revenue': timesheet_line.company_currency_id.round(line_revenue), })
def _compute_purchased_product_qty(self): date_from = fields.Datetime.to_string(fields.datetime.now() - timedelta(days=365)) domain = [ ('state', 'in', ['purchase', 'done']), ('product_id', 'in', self.mapped('id')), ('date_order', '>', date_from) ] PurchaseOrderLines = self.env['purchase.order.line'].search(domain) order_lines = self.env['purchase.order.line'].read_group(domain, ['product_id', 'product_uom_qty'], ['product_id']) purchased_data = dict([(data['product_id'][0], data['product_uom_qty']) for data in order_lines]) for product in self: product.purchased_product_qty = float_round(purchased_data.get(product.id, 0), precision_rounding=product.uom_id.rounding)
def _get_remaining_qty(self): if self.package_id and not self.product_id: # dont try to compute the remaining quantity for packages because it's not relevant (a package could include different products). # should use _get_remaining_prod_quantities instead # TDE FIXME: actually resolve the comment hereabove self.remaining_qty = 0 else: qty = self.product_qty if self.product_uom_id: qty = self.product_uom_id._compute_quantity(self.product_qty, self.product_id.uom_id) for record in self.linked_move_operation_ids: qty -= record.qty self.remaining_qty = float_round(qty, precision_rounding=self.product_id.uom_id.rounding)
def _prepare_stock_return_picking_line_vals_from_move(self, stock_move): quantity = stock_move.product_qty - sum( stock_move.move_dest_ids .filtered(lambda m: m.state in ['partially_available', 'assigned', 'done']) .mapped('move_line_ids.product_qty') ) quantity = float_round(quantity, precision_rounding=stock_move.product_uom.rounding) return { 'product_id': stock_move.product_id.id, 'quantity': quantity, 'move_id': stock_move.id, 'uom_id': stock_move.product_id.uom_id.id, }
def _compute_quantities_dict(self, lot_id, owner_id, package_id, from_date=False, to_date=False): domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self._get_domain_locations() domain_quant = [('product_id', 'in', self.ids)] + domain_quant_loc domain_move_in = [('state', 'not in', ('done', 'cancel', 'draft')), ('product_id', 'in', self.ids)] + domain_move_in_loc domain_move_out = [('state', 'not in', ('done', 'cancel', 'draft')), ('product_id', 'in', self.ids)] + domain_move_out_loc if from_date: domain_move_in += [('date', '>=', from_date)] domain_move_out += [('date', '>=', from_date)] if to_date: domain_move_in += [('date', '<=', to_date)] domain_move_out += [('date', '<=', to_date)] if lot_id: domain_quant += [('lot_id', '=', lot_id)] if owner_id: domain_quant += [('owner_id', '=', owner_id)] domain_move_in += [('restrict_partner_id', '=', owner_id)] domain_move_out += [('restrict_partner_id', '=', owner_id)] if package_id: domain_quant += [('package_id', '=', package_id)] Move = self.env['stock.move'] Quant = self.env['stock.quant'] moves_in_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in, ['product_id', 'product_qty'], ['product_id'])) moves_out_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out, ['product_id', 'product_qty'], ['product_id'])) quants_res = dict((item['product_id'][0], item['qty']) for item in Quant.read_group(domain_quant, ['product_id', 'qty'], ['product_id'])) res = dict() for product in self.with_context(prefetch_fields=False): res[product.id] = {} res[product.id]['qty_available'] = float_round(quants_res.get(product.id, 0.0), precision_rounding=product.uom_id.rounding) res[product.id]['incoming_qty'] = float_round(moves_in_res.get(product.id, 0.0), precision_rounding=product.uom_id.rounding) res[product.id]['outgoing_qty'] = float_round(moves_out_res.get(product.id, 0.0), precision_rounding=product.uom_id.rounding) res[product.id]['virtual_available'] = float_round( res[product.id]['qty_available'] + res[product.id]['incoming_qty'] - res[product.id]['outgoing_qty'], precision_rounding=product.uom_id.rounding) return res
def _compute_sales_count(self): r = {} if not self.user_has_groups('sales_team.group_sale_salesman'): return r date_from = fields.Datetime.to_string(fields.datetime.now() - timedelta(days=365)) domain = [ ('state', 'in', ['sale', 'done']), ('product_id', 'in', self.ids), ('date', '>', date_from) ] for group in self.env['sale.report'].read_group(domain, ['product_id', 'product_uom_qty'], ['product_id']): r[group['product_id'][0]] = group['product_uom_qty'] for product in self: product.sales_count = float_round(r.get(product.id, 0), precision_rounding=product.uom_id.rounding) return r
def _create_stripe_refund(self): api_url_refund = 'https://%s/refunds' % (self.acquirer_id._get_stripe_api_url()) refund_params = { 'charge': self.acquirer_reference, 'amount': int(float_round(self.amount * 100, 2)), # by default, stripe refund the full amount (we don't really need to specify the value) 'metadata[reference]': self.reference, } _logger.info('_create_stripe_refund: Sending values to URL %s, values:\n%s', api_url_refund, pprint.pformat(refund_params)) r = requests.post(api_url_refund, auth=(self.acquirer_id.stripe_secret_key, ''), params=refund_params, headers=STRIPE_HEADERS) res = r.json() _logger.info('_create_stripe_refund: Values received:\n%s', pprint.pformat(res)) return res
def value_to_html(self, value, options): if 'decimal_precision' in options: precision = self.env['decimal.precision'].search([('name', '=', options['decimal_precision'])]).digits else: precision = options['precision'] if precision is None: fmt = '%f' else: value = float_utils.float_round(value, precision_digits=precision) fmt = '%.{precision}f'.format(precision=precision) formatted = self.user_lang().format(fmt, value, grouping=True).replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}') # %f does not strip trailing zeroes. %g does but its precision causes # it to switch to scientific notation starting at a million *and* to # strip decimals. So use %f and if no precision was specified manually # strip trailing 0. if precision is None: formatted = re.sub(r'(?:(0|\d+?)0+)$', r'\1', formatted) return pycompat.to_text(formatted)
def value_to_html(self, value, options): if "decimal_precision" in options: precision = self.env["decimal.precision"].search([("name", "=", options["decimal_precision"])]).digits else: precision = options["precision"] if precision is None: fmt = "%f" else: value = float_utils.float_round(value, precision_digits=precision) fmt = "%.{precision}f".format(precision=precision) formatted = self.user_lang().format(fmt, value, grouping=True) # %f does not strip trailing zeroes. %g does but its precision causes # it to switch to scientific notation starting at a million *and* to # strip decimals. So use %f and if no precision was specified manually # strip trailing 0. if precision is None: formatted = re.sub(r"(?:(0|\d+?)0+)$", r"\1", formatted) return unicodifier(formatted)
def default_get(self, fields): if len(self.env.context.get('active_ids', list())) > 1: raise UserError(_("You may only return one picking at a time!")) res = super(ReturnPicking, self).default_get(fields) move_dest_exists = False product_return_moves = [] picking = self.env['stock.picking'].browse(self.env.context.get('active_id')) if picking: res.update({'picking_id': picking.id}) if picking.state != 'done': raise UserError(_("You may only return Done pickings")) for move in picking.move_lines: if move.scrapped: continue if move.move_dest_ids: move_dest_exists = True quantity = move.product_qty - sum(move.move_dest_ids.filtered(lambda m: m.state in ['partially_available', 'assigned', 'done']).\ mapped('move_line_ids').mapped('product_qty')) quantity = float_round(quantity, precision_rounding=move.product_uom.rounding) product_return_moves.append((0, 0, {'product_id': move.product_id.id, 'quantity': quantity, 'move_id': move.id, 'uom_id': move.product_id.uom_id.id})) if not product_return_moves: raise UserError(_("No products to return (only lines in Done state and not fully returned yet can be returned)!")) if 'product_return_moves' in fields: res.update({'product_return_moves': product_return_moves}) if 'move_dest_exists' in fields: res.update({'move_dest_exists': move_dest_exists}) if 'parent_location_id' in fields and picking.location_id.usage == 'internal': res.update({'parent_location_id': picking.picking_type_id.warehouse_id and picking.picking_type_id.warehouse_id.view_location_id.id or picking.location_id.location_id.id}) if 'original_location_id' in fields: res.update({'original_location_id': picking.location_id.id}) if 'location_id' in fields: location_id = picking.location_id.id if picking.picking_type_id.return_picking_type_id.default_location_dest_id.return_location: location_id = picking.picking_type_id.return_picking_type_id.default_location_dest_id.id res['location_id'] = location_id return res
def do_batch_transfer(self): """ Validate a batch of internal transfers. Only allow Internal Transfer type. Only allow Available pickings. Only allow totally incomplete picking lines. Skip transfers requiring serial numbers. Skip transfers requiring quality checks. """ for pick in self: if not pick.picking_type_id == self.env.ref('stock.picking_type_internal'): raise UserError(_('Transfers must be type "Internal Transfers". (%s)' % pick)) if not pick.state == 'assigned': raise UserError(_('Transfer must be in the "Available" state. (%s)' % pick)) if not all([x.qty_done == 0.0 for x in pick.pack_operation_ids]): raise UserError(_('We can only validate batches with NO completed lines. (%s)' % pick)) if not pick.move_lines and not pick.pack_operation_ids: raise UserError(_('Create some Initial Demand or Mark as Todo and create some Operations. (%s)' % pick)) picking_type = pick.picking_type_id serials_required = False if picking_type.use_create_lots or picking_type.use_existing_lots: for pack in pick.pack_operation_ids: if pack.product_id and pack.product_id.tracking != 'none': serials_required = True # raise UserError(_('Lots/serials required, specify those first! (%s)' % pick)) if pick.check_todo or serials_required: continue for pack in pick.pack_operation_ids: if pack.product_qty > 0: this_qty = float_round( pack.product_qty, precision_rounding=self.product_id.uom_id.rounding) pack.write({'qty_done': this_qty}) else: pack.unlink() pick.do_transfer()
def _put_in_pack_custom(self): package = False for pick in self.filtered(lambda p: p.state not in ('done', 'cancel')): move_line_ids = pick.move_line_ids_without_package.filtered(lambda o: not o.result_package_id) if move_line_ids : #or move_line_ids_2 move_lines_to_pack = self.env['stock.move.line'] package = self.env['stock.quant.package'].create({}) for ml in move_line_ids: if float_compare(ml.qty_done, ml.product_uom_qty, precision_rounding=ml.product_uom_id.rounding) >= 0: move_lines_to_pack |= ml else: quantity_left_todo = float_round( ml.product_uom_qty - ml.qty_done, precision_rounding=ml.product_uom_id.rounding, rounding_method='UP') done_to_keep = ml.qty_done new_move_line = ml.copy( default={'product_uom_qty': 0, 'qty_done': ml.qty_done}) ml.write({'product_uom_qty': quantity_left_todo, 'qty_done': 0.0}) new_move_line.write({'product_uom_qty': done_to_keep}) move_lines_to_pack |= new_move_line package_level = self.env['stock.package_level'].create({ 'package_id': package.id, 'picking_id': pick.id, 'location_id': False, 'location_dest_id': move_line_ids.mapped('location_dest_id').id, 'move_line_ids': [(6, 0, move_lines_to_pack.ids)] }) move_lines_to_pack.write({ 'result_package_id': package.id, }) else: raise UserError(_('You must first set the quantity you will put in the pack.')) return package.id
def _compute_total_display_difference(self): for invoice in self: invoice.check_total_display_difference = float_round( invoice.check_total - invoice.amount_total, precision_rounding=invoice.currency_id.rounding)
def get_a_field_val( self, field_name, field_attr, needdata, row, sheet, check_file, sheet_of_copy_wb, merge_tuple_list, model_name, noti_dict, key_search_dict, update_dict, # x2m_fields, collection_dict, setting, sheet_of_copy_wb_para): skip_this_field = get_key(field_attr, 'skip_this_field', False) if callable(skip_this_field): skip_this_field = skip_this_field(self) if skip_this_field: return 'continue' col_index = get_key(field_attr, 'col_index') func = get_key(field_attr, 'func') #F11 obj, val = read_val_for_ci(self, field_attr, check_file, needdata, noti_dict, setting, excel_para={ 'col_index': col_index, 'sheet': sheet, 'row': row, 'merge_tuple_list': merge_tuple_list, 'sheet_of_copy_wb': sheet_of_copy_wb }, for_print_para={ 'model_name': model_name, 'field_name': field_name }, sheet_of_copy_wb_para=sheet_of_copy_wb_para) field_attr['before_func_val'] = val # func karg = get_key(field_attr, 'karg', {}) if karg == None: karg = {} func_pre_func = field_attr.get('func_pre_func') if func_pre_func: val = func_pre_func(val, needdata, self) if func: try: val = func(val, needdata, **karg) except TypeError: try: val = func(val, needdata, self, **karg) except TypeError: val = func(val, **karg) # print ('func read model_name:%s field_name:%s'%(model_name,field_name),'val',val) val = replace_val_for_ci(field_attr, val, needdata) field_attr['val_goc'] = val if val == False: default_val = field_attr.get('default_val') if default_val != None: val = default_val if field_attr.get('field_type') == 'float': val = float_round(val, precision_rounding=0.01) field_attr['val'] = val field_attr['obj'] = obj if check_file: required_when_normal = get_key(field_attr, 'required', False) required = get_key(field_attr, 'required_when_check_file', required_when_normal) if (required_when_normal and val == False) and required == False: collection_dict['instance_false'] = True else: required = get_key(field_attr, 'required', False) key_or_not = field_attr.get('key') if '2many' in field_attr.get('field_type', '') and val == False: a_field_code = 'continue' return a_field_code if required and (val == False and isinstance( val, bool)): # val ==False <==> val ==0, val ==0 <==> val =False this_model_notice = noti_dict.setdefault(model_name, {}) skip_because_required = this_model_notice.setdefault( 'skip_because_required', 0) this_model_notice['skip_because_required'] = skip_because_required + 1 a_field_code = 'break_out_a_row_because_a_required' return a_field_code #sua 5 elif not field_attr.get('for_excel_readonly'): if key_or_not == True: key_search_dict[field_name] = val elif key_or_not == 'Both': key_search_dict[field_name] = val update_dict[field_name] = val else: update_dict[field_name] = val valid_field_func = field_attr.get('valid_field_func') if valid_field_func: valid_field_func(val, obj, needdata, self) print("row: ", row, 'model_name: ', model_name, '-field: ', field_name, '-val: ', val) check_type_of_val(field_attr, val, field_name, model_name) a_field_code = False return a_field_code
def _compute_mrp_product_qty(self): for template in self: template.mrp_product_qty = float_round(sum(template.mapped('product_variant_ids').mapped('mrp_product_qty')), precision_rounding=template.uom_id.rounding)
def onchange_check_total(self): self.check_total_display_difference = float_round( self.check_total - self.amount_total, precision_rounding=self.currency_id.rounding)
def _stripe_create_payment_intent(self, acquirer_ref=None, email=None): if not self.payment_token_id.stripe_payment_method: # old token before using sca, need to fetch data from the api self.payment_token_id._stripe_sca_migrate_customer() charge_params = { 'amount': int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)), 'currency': self.currency_id.name.lower(), 'off_session': True, 'confirm': True, 'payment_method': self.payment_token_id.stripe_payment_method, 'customer': self.payment_token_id.acquirer_ref, "description": self.reference, } if not self.env.context.get('off_session'): charge_params.update(setup_future_usage='off_session', off_session=False) _logger.info('_stripe_create_payment_intent: Sending values to stripe, values:\n%s', pprint.pformat(charge_params)) res = self.acquirer_id._stripe_request('payment_intents', charge_params) if res.get('charges') and res.get('charges').get('total_count'): res = res.get('charges').get('data')[0] _logger.info('_stripe_create_payment_intent: Values received:\n%s', pprint.pformat(res)) return res
def _compute_years_in(self): for me in self: if me.employee_id: contract_id = me.env["hr.contract"].search([ ("employee_id", "=", me.employee_id.id), ("date_start", "<=", me.date_stop), ("date_end", ">=", me.date_stop), ]) remaining = me.employee_id._get_paid_remaining_leaves() me.days_paid_holidays = float_round(remaining.get( me.employee_id.id, 0.0), precision_digits=2) difference_in_years = relativedelta(me.date_stop, me.date_in) me.years_in = difference_in_years.years me.months_in = difference_in_years.months me.days_in = difference_in_years.days if contract_id: if contract_id.structure_type_id.is_allow: amount_res = (contract_id[0].wage + contract_id[0].amount_hous + contract_id[0].amount_trasportation + contract_id[0].amount_anuther_allow) else: amount_res = (contract_id[0].wage + contract_id[0].amount_anuther_allow) me.amount_days_paid_holidays = round( (amount_res / 30) * me.days_paid_holidays, 2) else: me.amount_days_paid_holidays = 0.0 if me.type_end == 'Resignation': me.amount_in_days = ((me.years_in * 365) + (me.months_in * 30) + (me.days_in)) / 365 try: if me.years_in < 2: me.sum_lastes = 0.0 elif me.years_in >= 2 and me.years_in < 5: me.sum_lastes = (33.33 / 100) * me.amount_in_days * ( amount_res / 2) elif me.years_in >= 5 and me.years_in < 10: res11 = (66.66 / 100) * 5 * (amount_res / 2) res12 = (66.66 / 100) * (me.amount_in_days - 5) * (amount_res) me.sum_lastes = res11 + res12 elif me.years_in >= 10: res1 = 5 * (amount_res / 2) res3 = (me.amount_in_days - 5) * (amount_res) me.sum_lastes = res1 + res3 except: me.sum_lastes = 0.0 else: me.amount_in_days = ((me.years_in * 365) + (me.months_in * 30) + (me.days_in)) / 365 if me.amount_in_days <= 0.24: me.sum_lastes = 0.0 elif me.amount_in_days > 0.24 and me.amount_in_days <= 5: try: res3 = me.amount_in_days * ((amount_res) / 2) me.sum_lastes = res3 except: me.sum_lastes = 0.0 elif me.amount_in_days > 5: try: res3 = 5 * ((amount_res) / 2) res32 = (me.amount_in_days - 5) * (amount_res) me.sum_lastes = res3 + res32 except: me.sum_lastes = 0.0 accounts = [] monthly = me.env["advanced.salary.monthly"].search([ ("hr_employee", "=", me.employee_id.id) ]) sum_monthly = 0.0 for m in monthly: if m.account_id.id in accounts: continue else: accounts.append(m.account_id.id) sum_monthly += m.balance me.balance = sum_monthly me.net = me.sum_lastes + me.amount_days_paid_holidays - sum_monthly
def _compute_mrp_product_qty(self): self.mrp_product_qty = float_round( sum(self.mapped('product_variant_ids').mapped('mrp_product_qty')), precision_rounding=self.uom_id.rounding)
def stripe_form_generate_values(self, tx_values): self.ensure_one() base_url = self.get_base_url() stripe_session_data = { 'line_items[][amount]': int(tx_values['amount'] if tx_values['currency'].name in INT_CURRENCIES else float_round(tx_values['amount'] * 100, 2)), 'line_items[][currency]': tx_values['currency'].name, 'line_items[][quantity]': 1, 'line_items[][name]': tx_values['reference'], 'client_reference_id': tx_values['reference'], 'success_url': urls.url_join(base_url, StripeController._success_url) + '?reference=%s' % tx_values['reference'], 'cancel_url': urls.url_join(base_url, StripeController._cancel_url) + '?reference=%s' % tx_values['reference'], 'payment_intent_data[description]': tx_values['reference'], 'customer_email': tx_values.get('partner_email') or tx_values.get('billing_partner_email'), } self._add_available_payment_method_types(stripe_session_data, tx_values) tx_values['session_id'] = self.with_context(stripe_manual_payment=True)._create_stripe_session(stripe_session_data) return tx_values
def _action_done(self): """ This method is called during a move's `action_done`. It'll actually move a quant from the source location to the destination location, and unreserve if needed in the source location. This method is intended to be called on all the move lines of a move. This method is not intended to be called when editing a `done` move (that's what the override of `write` here is done. """ Quant = self.env['stock.quant'] # First, we loop over all the move lines to do a preliminary check: `qty_done` should not # be negative and, according to the presence of a picking type or a linked inventory # adjustment, enforce some rules on the `lot_id` field. If `qty_done` is null, we unlink # the line. It is mandatory in order to free the reservation and correctly apply # `action_done` on the next move lines. ml_to_delete = self.env['stock.move.line'] for ml in self: # Check here if `ml.qty_done` respects the rounding of `ml.product_uom_id`. uom_qty = float_round( ml.qty_done, precision_rounding=ml.product_uom_id.rounding, rounding_method='HALF-UP') precision_digits = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') qty_done = float_round(ml.qty_done, precision_digits=precision_digits, rounding_method='HALF-UP') if float_compare( uom_qty, qty_done, precision_digits=precision_digits) != 0: raise UserError( _('The quantity done for the product "%s" doesn\'t respect the rounding precision \ defined on the unit of measure "%s". Please change the quantity done or the \ rounding precision of your unit of measure.') % (ml.product_id.display_name, ml.product_uom_id.name)) qty_done_float_compared = float_compare( ml.qty_done, 0, precision_rounding=ml.product_uom_id.rounding) if qty_done_float_compared > 0: if ml.product_id.tracking != 'none': picking_type_id = ml.move_id.picking_type_id if picking_type_id: if picking_type_id.use_create_lots: # If a picking type is linked, we may have to create a production lot on # the fly before assigning it to the move line if the user checked both # `use_create_lots` and `use_existing_lots`. ml._create_and_assign_production_lot() elif not picking_type_id.use_create_lots and not picking_type_id.use_existing_lots: # If the user disabled both `use_create_lots` and `use_existing_lots` # checkboxes on the picking type, he's allowed to enter tracked # products without a `lot_id`. continue elif ml.move_id.inventory_id: # If an inventory adjustment is linked, the user is allowed to enter # tracked products without a `lot_id`. continue if not ml.lot_id: raise UserError( _('You need to supply a Lot/Serial number for product %s.' ) % ml.product_id.display_name) elif qty_done_float_compared < 0: raise UserError(_('No negative quantities allowed')) else: ml_to_delete |= ml ml_to_delete.unlink() (self - ml_to_delete)._check_company() # Now, we can actually move the quant. done_ml = self.env['stock.move.line'] for ml in self - ml_to_delete: if ml.product_id.type == 'product': rounding = ml.product_uom_id.rounding # if this move line is force assigned, unreserve elsewhere if needed if not ml._should_bypass_reservation( ml.location_id) and float_compare( ml.qty_done, ml.product_qty, precision_rounding=rounding) > 0: extra_qty = ml.qty_done - ml.product_qty ml._free_reservation(ml.product_id, ml.location_id, extra_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, ml_to_ignore=done_ml) # unreserve what's been reserved if not ml._should_bypass_reservation( ml.location_id ) and ml.product_id.type == 'product' and ml.product_qty: try: Quant._update_reserved_quantity( ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) except UserError: Quant._update_reserved_quantity( ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) # move what's been actually done quantity = ml.product_uom_id._compute_quantity( ml.qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP') available_qty, in_date = Quant._update_available_quantity( ml.product_id, ml.location_id, -quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id) if available_qty < 0 and ml.lot_id: # see if we can compensate the negative quants with some untracked quants untracked_qty = Quant._get_available_quantity( ml.product_id, ml.location_id, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) if untracked_qty: taken_from_untracked_qty = min(untracked_qty, abs(quantity)) Quant._update_available_quantity( ml.product_id, ml.location_id, -taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id) Quant._update_available_quantity( ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id) Quant._update_available_quantity( ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id, in_date=in_date) done_ml |= ml # Reset the reserved quantity as we just moved it to the destination location. (self - ml_to_delete).with_context(bypass_reservation_update=True).write({ 'product_uom_qty': 0.00, 'date': fields.Datetime.now(), })
def _compute_quantities_product_quant_dic(self, lot_id, owner_id, package_id, from_date, to_date, product_obj, data): loc_list = [] domain_quant_loc, domain_move_in_loc, domain_move_out_loc = product_obj._get_domain_locations( ) custom_domain = [] if data['company_id']: obj = self.env['res.company'].search([('name', '=', data['company_id'])]) custom_domain.append(('company_id', '=', obj.id)) if data['location_id']: custom_domain.append(('location_id', '=', data['location_id'].id)) if data['warehouse']: ware_check_domain = [a.id for a in data['warehouse']] locations = [] for i in ware_check_domain: loc_ids = self.env['stock.warehouse'].search([('id', '=', i)]) locations.append(loc_ids.view_location_id.id) for i in loc_ids.view_location_id.child_ids: locations.append(i.id) loc_list.append(loc_ids.lot_stock_id.id) custom_domain.append(('location_id', 'in', locations)) domain_quant = [('product_id', 'in', product_obj.ids) ] + domain_quant_loc + custom_domain #print ("dddddddddddddddddddddddddddddddddddddddddd",domain_quant) dates_in_the_past = False # only to_date as to_date will correspond to qty_available #to_date = fields.Datetime.to_datetime(to_date) if to_date and to_date < date.today(): dates_in_the_past = True domain_move_in = [('product_id', 'in', product_obj.ids) ] + domain_move_in_loc domain_move_out = [('product_id', 'in', product_obj.ids) ] + domain_move_out_loc if lot_id is not None: domain_quant += [('lot_id', '=', lot_id)] if owner_id is not None: domain_quant += [('owner_id', '=', owner_id)] domain_move_in += [('restrict_partner_id', '=', owner_id)] domain_move_out += [('restrict_partner_id', '=', owner_id)] if package_id is not None: domain_quant += [('package_id', '=', package_id)] if dates_in_the_past: domain_move_in_done = list(domain_move_in) domain_move_out_done = list(domain_move_out) if from_date: domain_move_in += [('date', '>=', from_date)] domain_move_out += [('date', '>=', from_date)] if to_date: domain_move_in += [('date', '<=', to_date)] domain_move_out += [('date', '<=', to_date)] Move = self.env['stock.move'] Quant = self.env['stock.quant'] domain_move_in_todo = [ ('state', 'in', ('waiting', 'confirmed', 'assigned', 'partially_available')) ] + domain_move_in domain_move_out_todo = [ ('state', 'in', ('waiting', 'confirmed', 'assigned', 'partially_available')) ] + domain_move_out moves_in_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group( domain_move_in_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id')) moves_out_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group( domain_move_out_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id')) quants_res = dict( (item['product_id'][0], item['quantity']) for item in Quant.read_group( domain_quant, ['product_id', 'quantity'], ['product_id'], orderby='id')) if dates_in_the_past: # Calculate the moves that were done before now to calculate back in time (as most questions will be recent ones) domain_move_in_done = [('state', '=', 'done'), ('date', '>', to_date) ] + domain_move_in_done domain_move_out_done = [('state', '=', 'done'), ('date', '>', to_date) ] + domain_move_out_done moves_in_res_past = dict( (item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_done, ['product_id', 'product_qty'], ['product_id'], orderby='id')) moves_out_res_past = dict( (item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_done, ['product_id', 'product_qty'], ['product_id'], orderby='id')) res = dict() for product in product_obj.with_context(prefetch_fields=False): product_id = product.id rounding = product.uom_id.rounding res[product_id] = {} if dates_in_the_past: qty_available = quants_res.get( product_id, 0.0) - moves_in_res_past.get( product_id, 0.0) + moves_out_res_past.get( product_id, 0.0) else: qty_available = quants_res.get(product_id, 0.0) res[product_id]['qty_available'] = float_round( qty_available, precision_rounding=rounding) res[product_id]['incoming_qty'] = float_round( moves_in_res.get(product_id, 0.0), precision_rounding=rounding) res[product_id]['outgoing_qty'] = float_round( moves_out_res.get(product_id, 0.0), precision_rounding=rounding) res[product_id]['virtual_available'] = float_round( qty_available + res[product_id]['incoming_qty'] - res[product_id]['outgoing_qty'], precision_rounding=rounding) return res
from odoo.tools import float_utils float_utils.float_round(1.5424, precision_digits=3, rounding_method='DOWN') # 1.542 float_utils.float_round(1.5424, precision_digits=3, rounding_method='UP') float_utils.float_round(1.5424, precision_digits=3, rounding_method='HALF-UP') # 1.542 float_utils.float_round(49, precision_rounding=100, rounding_method='HALF-UP') # 0.0 float_utils.float_round(50, precision_rounding=100, rounding_method='HALF-UP') # 100.0 float_utils.float_is_zero(0.04252, precision_digits=5) # False float_utils.float_is_zero(0.04252, precision_digits=1) # True float_utils.float_compare(0.042555, 0.04256, precision_digits=5) # 0 => Равны float_utils.float_compare(0.042555, 0.04256, precision_digits=6) # -1 => Первое меньше второго float_utils.float_compare(0.04256, 0.042555, precision_digits=6) # 1 => Первое больше второго
def test_01_compute_price_operation_cost(self): """Test calcuation of bom cost with operations.""" workcenter_from1 = Form(self.env['mrp.workcenter']) workcenter_from1.name = 'Workcenter' workcenter_from1.time_efficiency = 100 workcenter_from1.capacity = 2 workcenter_from1.oee_target = 100 workcenter_from1.time_start = 0 workcenter_from1.time_stop = 0 workcenter_from1.costs_hour = 100 workcenter_1 = workcenter_from1.save() routing_form1 = Form(self.Routing) routing_form1.name = 'Assembly Furniture' routing_1 = routing_form1.save() operation_1 = self.operation.create({ 'name': 'Cutting', 'workcenter_id': workcenter_1.id, 'routing_id': routing_1.id, 'time_mode': 'manual', 'time_cycle_manual': 20, 'batch': 'no', 'sequence': 1, }) operation_2 = self.operation.create({ 'name': 'Drilling', 'workcenter_id': workcenter_1.id, 'routing_id': routing_1.id, 'time_mode': 'manual', 'time_cycle_manual': 25, 'batch': 'no', 'sequence': 2, }) operation_3 = self.operation.create({ 'name': 'Fitting', 'workcenter_id': workcenter_1.id, 'routing_id': routing_1.id, 'time_mode': 'manual', 'time_cycle_manual': 30, 'batch': 'no', 'sequence': 3, }) # ----------------------------------------------------------------- # Dinning Table Operation Cost(1 Unit) # ----------------------------------------------------------------- # Operation cost calculate for 1 units # Cutting (20 / 60) * 100 = 33.33 # Drilling (25 / 60) * 100 = 41.67 # Fitting (30 / 60) * 100 = 50.00 # ---------------------------------------- # Operation Cost 1 unit = 125 # ----------------------------------------------------------------- self.bom_1.routing_id = routing_1.id # -------------------------------------------------------------------------- # Table Head Operation Cost (1 Dozen) # -------------------------------------------------------------------------- # Operation cost calculate for 1 dozens # Cutting (20 * 1 / 60) * 100 = 33,33 # Drilling (25 * 1 / 60) * 100 = 41,67 # Fitting (30 * 1 / 60) * 100 = 50 # ---------------------------------------- # Operation Cost 1 dozen (125 per dozen) and 10.42 for 1 Unit # -------------------------------------------------------------------------- self.bom_2.routing_id = routing_1.id self.assertEqual(self.dining_table.standard_price, 1000, "Initial price of the Product should be 1000") self.dining_table.button_bom_cost() # Total cost of Dining Table = (550) + Total cost of operations (125) = 675.0 self.assertEqual( float_round(self.dining_table.standard_price, precision_digits=2), 675.0, "After computing price from BoM price should be 612.5") self.Product.browse([self.dining_table.id, self.table_head.id]).action_bom_cost() # Total cost of Dining Table = (718.75) + Total cost of all operations (125 + 10.42) = 854.17 self.assertEqual( float_compare(self.dining_table.standard_price, 854.17, precision_digits=2), 0, "After computing price from BoM price should be 786.46")
def _onchange_hours_per_day(self): attendances = self.attendance_ids.filtered(lambda attendance: not attendance.date_from and not attendance.date_to) hour_count = 0.0 for attendance in attendances: hour_count += attendance.hour_to - attendance.hour_from self.hours_per_day = float_round(hour_count / float(len(set(attendances.mapped('dayofweek')))), precision_digits=2)
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): '''Create a quant in the destination location and create a negative quant in the source location if it's an internal location. ''' price_unit = move.get_price_unit() location = force_location_to or move.location_dest_id rounding = move.product_id.uom_id.rounding vals = { 'product_id': move.product_id.id, 'location_id': location.id, 'qty': float_round(qty, precision_rounding=rounding), 'cost': price_unit, 'history_ids': [(4, move.id)], 'in_date': datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'company_id': move.company_id.id, 'lot_id': lot_id, 'owner_id': owner_id, 'package_id': dest_package_id, } if move.location_id.usage == 'internal': # if we were trying to move something from an internal location and reach here (quant creation), # it means that a negative quant has to be created as well. negative_vals = vals.copy() negative_vals[ 'location_id'] = force_location_from and force_location_from.id or move.location_id.id negative_vals['qty'] = float_round(-qty, precision_rounding=rounding) negative_vals['cost'] = price_unit negative_vals['negative_move_id'] = move.id negative_vals['package_id'] = src_package_id negative_quant_id = self.sudo().create(negative_vals) vals.update({'propagated_from_id': negative_quant_id.id}) # In case of serial tracking, check if the product does not exist somewhere internally already picking_type = move.picking_id and move.picking_id.picking_type_id or False if lot_id and move.product_id.tracking == 'serial' and ( not picking_type or (picking_type.use_create_lots or picking_type.use_existing_lots)): if qty != 1.0: raise UserError( _('You should only receive by the piece with the same serial number' )) other_quants = self.search([ ('product_id', '=', move.product_id.id), ('lot_id', '=', lot_id), ('qty', '>', 0.0), ('location_id.usage', '=', 'internal') ]) if other_quants: lot_name = self.env['stock.production.lot'].browse(lot_id).name raise UserError( _('The serial number %s is already in stock.') % lot_name + _("Otherwise make sure the right stock/owner is set.")) # create the quant as superuser, because we want to restrict the creation of quant manually: we should always use this method to create quants return self.sudo().create(vals)
def _get_formatted_amount(self): amount = self.collection._get_amount_to_capture() if self.collection.currency_id.name in ZERO_DECIMAL_CURRENCIES: return int(amount) else: return int(float_round(amount * 100, 0))
def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location_from=False, force_location_to=False, context=None): '''Create a quant in the destination location and create a negative quant in the source location if it's an internal location. ''' if context is None: context = {} price_unit = self.pool.get('stock.move').get_price_unit( cr, uid, move, context=context) location = force_location_to or move.location_dest_id rounding = move.product_id.uom_id.rounding vals = { 'product_id': move.product_id.id, 'location_id': location.id, 'qty': float_round(qty, precision_rounding=rounding), 'cost': price_unit, 'history_ids': [(4, move.id)], 'in_date': datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'company_id': move.company_id.id, 'lot_id': lot_id, 'owner_id': owner_id, 'package_id': dest_package_id, 'description': move.name, 'price_unit': move.price_unit, 'currency_id': move.currency_id.id, 'quant_line_tax_ids': [(6, 0, move.move_line_tax_ids.ids)], } if move.location_id.usage == 'internal': #if we were trying to move something from an internal location and reach here (quant creation), #it means that a negative quant has to be created as well. negative_vals = vals.copy() negative_vals[ 'location_id'] = force_location_from and force_location_from.id or move.location_id.id negative_vals['qty'] = float_round(-qty, precision_rounding=rounding) negative_vals['cost'] = price_unit negative_vals['negative_move_id'] = move.id negative_vals['package_id'] = src_package_id negative_quant_id = self.create(cr, SUPERUSER_ID, negative_vals, context=context) vals.update({'propagated_from_id': negative_quant_id}) picking_type = move.picking_id and move.picking_id.picking_type_id or False if lot_id and move.product_id.tracking == 'serial' and ( not picking_type or (picking_type.use_create_lots or picking_type.use_existing_lots)): if qty != 1.0: raise UserError( _('You should only receive by the piece with the same serial number' )) #create the quant as superuser, because we want to restrict the creation of quant manually: we should always use this method to create quants quant_id = self.create(cr, SUPERUSER_ID, vals, context=context) return self.browse(cr, uid, quant_id, context=context)
def _compute_sale_total(self): for template in self: template.sales_count = float_round( sum([p.sales_count for p in template.product_variant_ids]), precision_rounding=template.uom_id.rounding)
def _stripe_form_get_invalid_parameters(self, data): invalid_parameters = [] if data.get('amount') != int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)): invalid_parameters.append(('Amount', data.get('amount'), self.amount * 100)) if data.get('currency').upper() != self.currency_id.name: invalid_parameters.append(('Currency', data.get('currency'), self.currency_id.name)) if data.get('payment_intent') and data.get('payment_intent') != self.stripe_payment_intent: invalid_parameters.append(('Payment Intent', data.get('payment_intent'), self.stripe_payment_intent)) return invalid_parameters
def _compute_quantities_dict(self, lot_id, owner_id, package_id, from_date=False, to_date=False): domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self._get_domain_locations( ) domain_quant = [('product_id', 'in', self.ids)] + domain_quant_loc dates_in_the_past = False # only to_date as to_date will correspond to qty_available to_date = fields.Datetime.to_datetime(to_date) if to_date and to_date < fields.Datetime.now(): dates_in_the_past = True domain_move_in = [('product_id', 'in', self.ids)] + domain_move_in_loc domain_move_out = [('product_id', 'in', self.ids) ] + domain_move_out_loc if lot_id is not None: domain_quant += [('lot_id', '=', lot_id)] if owner_id is not None: domain_quant += [('owner_id', '=', owner_id)] domain_move_in += [('restrict_partner_id', '=', owner_id)] domain_move_out += [('restrict_partner_id', '=', owner_id)] if package_id is not None: domain_quant += [('package_id', '=', package_id)] if dates_in_the_past: domain_move_in_done = list(domain_move_in) domain_move_out_done = list(domain_move_out) if from_date: domain_move_in += [('date', '>=', from_date)] domain_move_out += [('date', '>=', from_date)] if to_date: domain_move_in += [('date', '<=', to_date)] domain_move_out += [('date', '<=', to_date)] Move = self.env['stock.move'] Quant = self.env['stock.quant'] domain_move_in_todo = [ ('state', 'in', ('waiting', 'confirmed', 'assigned', 'partially_available')) ] + domain_move_in domain_move_out_todo = [ ('state', 'in', ('waiting', 'confirmed', 'assigned', 'partially_available')) ] + domain_move_out moves_in_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group( domain_move_in_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id')) moves_out_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group( domain_move_out_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id')) quants_res = dict( (item['product_id'][0], item['quantity']) for item in Quant.read_group( domain_quant, ['product_id', 'quantity'], ['product_id'], orderby='id')) if dates_in_the_past: # Calculate the moves that were done before now to calculate back in time (as most questions will be recent ones) domain_move_in_done = [('state', '=', 'done'), ('date', '>', to_date) ] + domain_move_in_done domain_move_out_done = [('state', '=', 'done'), ('date', '>', to_date) ] + domain_move_out_done moves_in_res_past = dict( (item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_done, ['product_id', 'product_qty'], ['product_id'], orderby='id')) moves_out_res_past = dict( (item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_done, ['product_id', 'product_qty'], ['product_id'], orderby='id')) res = dict() for product in self.with_context(prefetch_fields=False): product_id = product.id rounding = product.uom_id.rounding res[product_id] = {} if dates_in_the_past: qty_available = quants_res.get( product_id, 0.0) - moves_in_res_past.get( product_id, 0.0) + moves_out_res_past.get( product_id, 0.0) else: qty_available = quants_res.get(product_id, 0.0) res[product_id]['qty_available'] = float_round( qty_available, precision_rounding=rounding) res[product_id]['incoming_qty'] = float_round( moves_in_res.get(product_id, 0.0), precision_rounding=rounding) res[product_id]['outgoing_qty'] = float_round( moves_out_res.get(product_id, 0.0), precision_rounding=rounding) res[product_id]['virtual_available'] = float_round( qty_available + res[product_id]['incoming_qty'] - res[product_id]['outgoing_qty'], precision_rounding=rounding) return res
def default_get(self, fields_list): res = super(ReturnPicking, self).default_get(fields_list) if res.get('picking_id') and res.get('product_return_moves'): product_return_moves = [] picking = self.env['stock.picking'].browse(res['picking_id']) if picking: res.update({ 'owner_id': picking.owner_id and picking.owner_id.id or False, }) res_move_id = set([]) quantity_new = {} for move_line in res['product_return_moves']: # _logger.info("product return moves %s" % (move_line,)) cmd, arg, value = move_line res_move_id.update([value['move_id']]) quantity_new[value['move_id']] = value['quantity'] move_lines = self.env['stock.move'].browse(list(res_move_id)) for move in move_lines: del_move_lines = self.env['stock.move.line'] quantitys = {} if move.state == 'cancel': continue if move.scrapped: continue current_move_lines = move.move_line_ids.filtered( lambda m: m.state in ['partially_available', 'assigned', 'done']) quantity_save = quantity_new[move.id] for move_line in current_move_lines: returned_ids = picking.mapped( 'move_lines.returned_move_ids.move_line_ids' ).filtered(lambda r: r.lot_id == move_line.lot_id) returned_quantity = sum( [x.qty_done for x in returned_ids]) if move_line.qty_done == returned_quantity: del_move_lines = move_line continue quantitys[ move_line] = move_line.qty_done - returned_quantity # for returned in returned_ids: # _logger.info("RETURN %s-%s" % (returned, move_line)) # if move_line.product_id == returned.product_id and move_line.lot_id == returned.lot_id: # if move_line.qty_done == returned.qty_done: # del_move_lines |= move_line # continue # quantitys[move_line] = move_line.qty_done - returned.qty_done # _logger.info("DEL LINES %s-%s=%s" % (del_move_lines, current_move_lines, current_move_lines - del_move_lines)) moves = current_move_lines - del_move_lines for owner_id, own_lines in groupby( moves.sorted(lambda r: r.owner_id, reverse=True), lambda l: l.owner_id): lines_own = [] for line in own_lines: lines_own.append(line) for product_set, lines in groupby( sorted(lines_own, key=lambda r: r.product_set_id, reverse=True), lambda l: l.product_set_id): lines_lot = [] for line in lines: lines_lot.append(line) for lot_id, line_lot in groupby( sorted(lines_lot, key=lambda k: k.lot_id, reverse=True), lambda r: r.lot_id): quantity = sum( [quantitys.get(r, 0.0) for r in line_lot]) quantity = float_round(quantity, precision_rounding=move. product_uom.rounding) # _logger.info("QTY %s(%s)==>%s=%s" % (product_set, [r.qty_done for r in moves.filtered(lambda r: r.product_set_id == product_set)], quantity, quantity_save)) quantity_save -= quantity product_return_moves.append((0, 0, { 'product_id': move.product_id.id, 'quantity': quantity, 'move_id': move.id, 'uom_id': move.product_id.uom_id.id, 'lot_id': lot_id and lot_id.id or False, 'owner_id': owner_id and owner_id.id or False, 'product_set_id': product_set.id })) if product_return_moves: res['product_return_moves'] = product_return_moves else: res['product_return_moves'] = False return res
def _free_reservation(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None): """ When editing a done move line or validating one with some forced quantities, it is possible to impact quants that were not reserved. It is therefore necessary to edit or unlink the move lines that reserved a quantity now unavailable. """ self.ensure_one() # Check the available quantity, with the `strict` kw set to `True`. If the available # quantity is greather than the quantity now unavailable, there is nothing to do. available_quantity = self.env['stock.quant']._get_available_quantity( product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True) if quantity > available_quantity: # We now have to find the move lines that reserved our now unavailable quantity. We # take care to exclude ourselves and the move lines were work had already been done. oudated_move_lines_domain = [ ('move_id.state', 'not in', ['done', 'cancel']), ('product_id', '=', product_id.id), ('lot_id', '=', lot_id.id if lot_id else False), ('location_id', '=', location_id.id), ('owner_id', '=', owner_id.id if owner_id else False), ('package_id', '=', package_id.id if package_id else False), ('product_qty', '>', 0.0), ('qty_done', '=', 0.0), ('id', '!=', self.id), ] oudated_candidates = self.env['stock.move.line'].search( oudated_move_lines_domain) # As the move's state is not computed over the move lines, we'll have to manually # recompute the moves which we adapted their lines. move_to_recompute_state = self.env['stock.move'] rounding = self.product_uom_id.rounding for candidate in oudated_candidates: if float_compare(candidate.product_qty, quantity, precision_rounding=rounding) <= 0: quantity -= candidate.product_qty move_to_recompute_state |= candidate.move_id candidate.unlink() else: # split this move line and assign the new part to our extra move quantity_split = float_round( candidate.product_qty - quantity, precision_rounding=self.product_uom_id.rounding, rounding_method='UP') candidate.product_uom_qty = self.product_id.uom_id._compute_quantity( quantity_split, self.product_uom_id, rounding_method='HALF-UP') quantity -= quantity_split move_to_recompute_state |= candidate.move_id if quantity == 0.0: break move_to_recompute_state._recompute_state()
def _compute_total_allocation_used(self): for employee in self: employee.allocation_used_count = float_round(employee.allocation_count - employee.remaining_leaves, precision_digits=2) employee.allocation_used_display = "%g" % employee.allocation_used_count
def _compute_sales_count(self): for product in self: product.sales_count = float_round(sum([p.sales_count for p in product.with_context(active_test=False).product_variant_ids]), precision_rounding=product.uom_id.rounding)
def test_01_compute_price_operation_cost(self): """Test calcuation of bom cost with operations.""" workcenter_from1 = Form(self.env['mrp.workcenter']) workcenter_from1.name = 'Workcenter' workcenter_from1.time_efficiency = 100 workcenter_from1.capacity = 2 workcenter_from1.oee_target = 100 workcenter_from1.time_start = 0 workcenter_from1.time_stop = 0 workcenter_from1.costs_hour = 100 workcenter_1 = workcenter_from1.save() routing_form1 = Form(self.Routing) routing_form1.name = 'Assembly Furniture' routing_1 = routing_form1.save() operation_1 = self.operation.create({ 'name': 'Cutting', 'workcenter_id': workcenter_1.id, 'routing_id': routing_1.id, 'time_mode': 'manual', 'time_cycle_manual': 20, 'batch': 'no', 'sequence': 1, }) operation_2 = self.operation.create({ 'name': 'Drilling', 'workcenter_id': workcenter_1.id, 'routing_id': routing_1.id, 'time_mode': 'manual', 'time_cycle_manual': 25, 'batch': 'no', 'sequence': 2, }) operation_3 = self.operation.create({ 'name': 'Fitting', 'workcenter_id': workcenter_1.id, 'routing_id': routing_1.id, 'time_mode': 'manual', 'time_cycle_manual': 30, 'batch': 'no', 'sequence': 3, }) # ----------------------------------------------------------------- # Dinning Table Operation Cost(1 Unit) # ----------------------------------------------------------------- # Operation cost calculate for 1 units # Cutting (20 / 60) * 100 = 33.33 # Drilling (25 / 60) * 100 = 41.67 # Fitting (30 / 60) * 100 = 50.00 # ---------------------------------------- # Operation Cost 1 unit = 125 # ----------------------------------------------------------------- self.bom_1.routing_id = routing_1.id # -------------------------------------------------------------------------- # Table Head Operation Cost (1 Dozen) # -------------------------------------------------------------------------- # Operation cost calculate for 1 dozens # Cutting (20 * 1 / 60) * 100 = 33,33 # Drilling (25 * 1 / 60) * 100 = 41,67 # Fitting (30 * 1 / 60) * 100 = 50 # ---------------------------------------- # Operation Cost 1 dozen (125 per dozen) and 10.42 for 1 Unit # -------------------------------------------------------------------------- self.bom_2.routing_id = routing_1.id self.assertEqual(self.dining_table.standard_price, 1000, "Initial price of the Product should be 1000") self.dining_table.button_bom_cost() # Total cost of Dining Table = (550) + Total cost of operations (125) = 675.0 self.assertEquals(float_round(self.dining_table.standard_price, precision_digits=2), 675.0, "After computing price from BoM price should be 612.5") self.Product.browse([self.dining_table.id, self.table_head.id]).action_bom_cost() # Total cost of Dining Table = (718.75) + Total cost of all operations (125 + 10.42) = 854.17 self.assertEquals(float_compare(self.dining_table.standard_price, 854.17, precision_digits=2), 0, "After computing price from BoM price should be 786.46")
def _compute_amount_total(self): super(AccountInvoiceTax, self)._compute_amount_total() currency = self.invoice_id.currency_id or \ self.invoice_id.company_id.currency_id self.amount_deductible = float_round( self.amount_total * self.deduction_rate, currency.decimal_places)
def _action_done(self): """ This method is called during a move's `action_done`. It'll actually move a quant from the source location to the destination location, and unreserve if needed in the source location. This method is intended to be called on all the move lines of a move. This method is not intended to be called when editing a `done` move (that's what the override of `write` here is done. """ # First, we loop over all the move lines to do a preliminary check: `qty_done` should not # be negative and, according to the presence of a picking type or a linked inventory # adjustment, enforce some rules on the `lot_id` field. If `qty_done` is null, we unlink # the line. It is mandatory in order to free the reservation and correctly apply # `action_done` on the next move lines. ml_to_delete = self.env['stock.move.line'] for ml in self: # Check here if `ml.qty_done` respects the rounding of `ml.product_uom_id`. uom_qty = float_round(ml.qty_done, precision_rounding=ml.product_uom_id.rounding, rounding_method='HALF-UP') precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure') qty_done = float_round(ml.qty_done, precision_digits=precision_digits, rounding_method='HALF-UP') if float_compare(uom_qty, qty_done, precision_digits=precision_digits) != 0: raise UserError(_('The quantity done for the product "%s" doesn\'t respect the rounding precision \ defined on the unit of measure "%s". Please change the quantity done or the \ rounding precision of your unit of measure.') % (ml.product_id.display_name, ml.product_uom_id.name)) qty_done_float_compared = float_compare(ml.qty_done, 0, precision_rounding=ml.product_uom_id.rounding) if qty_done_float_compared > 0: if ml.product_id.tracking != 'none': picking_type_id = ml.move_id.picking_type_id if picking_type_id: if picking_type_id.use_create_lots: # If a picking type is linked, we may have to create a production lot on # the fly before assigning it to the move line if the user checked both # `use_create_lots` and `use_existing_lots`. if ml.lot_name and not ml.lot_id: lot = self.env['stock.production.lot'].create( {'name': ml.lot_name, 'product_id': ml.product_id.id} ) ml.write({'lot_id': lot.id}) elif not picking_type_id.use_create_lots and not picking_type_id.use_existing_lots: # If the user disabled both `use_create_lots` and `use_existing_lots` # checkboxes on the picking type, he's allowed to enter tracked # products without a `lot_id`. continue elif ml.move_id.inventory_id: # If an inventory adjustment is linked, the user is allowed to enter # tracked products without a `lot_id`. continue if not ml.lot_id: raise UserError(_('You need to supply a Lot/Serial number for product %s.') % ml.product_id.display_name) elif qty_done_float_compared < 0: raise UserError(_('No negative quantities allowed')) else: ml_to_delete |= ml ml_to_delete.unlink() # Now, we can actually move the quant. done_ml = self.env['stock.move.line'] for ml in self - ml_to_delete: if ml.product_id.type == 'product': Quant = self.env['stock.quant'] rounding = ml.product_uom_id.rounding # if this move line is force assigned, unreserve elsewhere if needed if not ml.location_id.should_bypass_reservation() and float_compare(ml.qty_done, ml.product_qty, precision_rounding=rounding) > 0: extra_qty = ml.qty_done - ml.product_qty ml._free_reservation(ml.product_id, ml.location_id, extra_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, ml_to_ignore=done_ml) # unreserve what's been reserved if not ml.location_id.should_bypass_reservation() and ml.product_id.type == 'product' and ml.product_qty: try: Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) except UserError: Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) # move what's been actually done quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP') available_qty, in_date = Quant._update_available_quantity(ml.product_id, ml.location_id, -quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id) if available_qty < 0 and ml.lot_id: # see if we can compensate the negative quants with some untracked quants untracked_qty = Quant._get_available_quantity(ml.product_id, ml.location_id, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True) if untracked_qty: taken_from_untracked_qty = min(untracked_qty, abs(quantity)) Quant._update_available_quantity(ml.product_id, ml.location_id, -taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id) Quant._update_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id) Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id, in_date=in_date) done_ml |= ml # Reset the reserved quantity as we just moved it to the destination location. (self - ml_to_delete).with_context(bypass_reservation_update=True).write({ 'product_uom_qty': 0.00, 'date': fields.Datetime.now(), })
def get_write_data(self, line, col_dict): """ Returns value and style for cell """ cell_type = col_dict.get('type', 'string') field = col_dict.get('field') decimals = self.currency.decimal_places value = getattr(line, field, False) style = None allow = False if cell_type == 'many2one': val_name = getattr(value, 'name', '') val_display = getattr(value, 'display_name', '') if val_name: value = val_name elif val_display: value = val_display elif line._name == 'account_balance_report_partner': value = _("No partner allocated") elif cell_type == 'string': if getattr(line, 'account_group_id', False): style = self.format_bold else: style = None elif cell_type == 'amount': value = float_round(float(value), decimals) if getattr(line, 'account_group_id', False): style = self.format_amount_bold_right else: style = self.format_amount_right allow = True elif cell_type == 'amount_currency': currency = getattr(line, 'currency_id', False) \ or getattr(line, 'company_currency_id', False) \ or self.currency decimals = currency.decimal_places value = float_round(float(value), decimals) if getattr(line, 'account_group_id', False): style = self.format_amount_bold_right else: style = self.format_amount_right allow = True if value: if isinstance(value, (int, float)) \ and cell_type not in ('amount', 'amount_currency'): value = format(value, '.{}f'.format(decimals)) if not isinstance(value, str) \ and cell_type not in ('amount', 'amount_currency'): value = str(value) indent_field, indent_unit = self.get_indent_data(line, col_dict) if self.report.hierarchy_on != 'none' \ and indent_field and indent_unit \ and hasattr(line, indent_field): indent = ' ' * getattr(line, indent_field, 0) * indent_unit value = indent + value allow = True if allow and isinstance(value, float) \ and float_is_zero(value, decimals): value = format(value, '.{}f'.format(decimals)) return value, style, allow
def _compute_secondary_unit_qty_available(self): for product in self.filtered('stock_secondary_uom_id'): qty = product.qty_available / ( product.stock_secondary_uom_id.factor or 1.0) product.secondary_unit_qty_available = float_round( qty, precision_rounding=product.uom_id.rounding)
def _free_reservation(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, ml_ids_to_ignore=None): """ When editing a done move line or validating one with some forced quantities, it is possible to impact quants that were not reserved. It is therefore necessary to edit or unlink the move lines that reserved a quantity now unavailable. :param ml_ids_to_ignore: OrderedSet of `stock.move.line` ids that should NOT be unreserved """ self.ensure_one() if ml_ids_to_ignore is None: ml_ids_to_ignore = OrderedSet() ml_ids_to_ignore |= self.ids # Check the available quantity, with the `strict` kw set to `True`. If the available # quantity is greather than the quantity now unavailable, there is nothing to do. available_quantity = self.env['stock.quant']._get_available_quantity( product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True ) if quantity > available_quantity: # We now have to find the move lines that reserved our now unavailable quantity. We # take care to exclude ourselves and the move lines were work had already been done. outdated_move_lines_domain = [ ('state', 'not in', ['done', 'cancel']), ('product_id', '=', product_id.id), ('lot_id', '=', lot_id.id if lot_id else False), ('location_id', '=', location_id.id), ('owner_id', '=', owner_id.id if owner_id else False), ('package_id', '=', package_id.id if package_id else False), ('product_qty', '>', 0.0), ('id', 'not in', tuple(ml_ids_to_ignore)), ] # We take the current picking first, then the pickings with the latest scheduled date current_picking_first = lambda cand: ( cand.picking_id != self.move_id.picking_id, -(cand.picking_id.scheduled_date or cand.move_id.date).timestamp() if cand.picking_id or cand.move_id else -cand.id, ) outdated_candidates = self.env['stock.move.line'].search(outdated_move_lines_domain).sorted(current_picking_first) # As the move's state is not computed over the move lines, we'll have to manually # recompute the moves which we adapted their lines. move_to_recompute_state = self.env['stock.move'] to_unlink_candidate_ids = set() rounding = self.product_uom_id.rounding for candidate in outdated_candidates: if float_compare(candidate.product_qty, quantity, precision_rounding=rounding) <= 0: quantity -= candidate.product_qty if candidate.qty_done: move_to_recompute_state |= candidate.move_id candidate.product_uom_qty = 0.0 else: to_unlink_candidate_ids.add(candidate.id) if float_is_zero(quantity, precision_rounding=rounding): break else: # split this move line and assign the new part to our extra move quantity_split = float_round( candidate.product_qty - quantity, precision_rounding=self.product_uom_id.rounding, rounding_method='UP') candidate.product_uom_qty = self.product_id.uom_id._compute_quantity(quantity_split, candidate.product_uom_id, rounding_method='HALF-UP') move_to_recompute_state |= candidate.move_id break self.env['stock.move.line'].browse(to_unlink_candidate_ids).unlink() move_to_recompute_state._recompute_state()
def _compute_purchased_product_qty(self): for template in self: template.purchased_product_qty = float_round(sum([p.purchased_product_qty for p in template.product_variant_ids]), precision_rounding=template.uom_id.rounding)
def _create_stripe_charge(self, acquirer_ref=None, tokenid=None, email=None): api_url_charge = 'https://%s/charges' % (self.acquirer_id._get_stripe_api_url()) charge_params = { 'amount': int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)), 'currency': self.currency_id.name, 'metadata[reference]': self.reference, 'description': self.reference, } if acquirer_ref: charge_params['customer'] = acquirer_ref if tokenid: charge_params['card'] = str(tokenid) if email: charge_params['receipt_email'] = email.strip() _logger.info('_create_stripe_charge: Sending values to URL %s, values:\n%s', api_url_charge, pprint.pformat(charge_params)) r = requests.post(api_url_charge, auth=(self.acquirer_id.stripe_secret_key, ''), params=charge_params, headers=STRIPE_HEADERS) res = r.json() _logger.info('_create_stripe_charge: Values received:\n%s', pprint.pformat(res)) return res
def _get_margin_value(self, value, previous_value=0.0): margin = 0.0 if (value != previous_value) and (value != 0.0 and previous_value != 0.0): margin = float_round((float(value-previous_value) / previous_value or 1) * 100, precision_digits=2) return margin
def _compute_remaining_leaves(self): remaining = self._get_remaining_leaves() for employee in self: employee.remaining_leaves = float_round(remaining.get( employee.id, 0.0), precision_digits=2)