def _update_move_lines(self): """ update a move line to save the workorder line data""" self.ensure_one() if self.lot_id: move_lines = self.move_id.move_line_ids.filtered( lambda ml: ml.lot_id == self.lot_id and not ml.lot_produced_ids ) else: move_lines = self.move_id.move_line_ids.filtered( lambda ml: not ml.lot_id and not ml.lot_produced_ids) # Sanity check: if the product is a serial number and `lot` is already present in the other # consumed move lines, raise. if self.product_id.tracking != 'none' and not self.lot_id: raise UserError( _('Please enter a lot or serial number for %s !' % self.product_id.display_name)) if self.lot_id and self.product_id.tracking == 'serial' and self.lot_id in self.move_id.move_line_ids.filtered( lambda ml: ml.qty_done).mapped('lot_id'): raise UserError( _('You cannot consume the same serial number twice. Please correct the serial numbers encoded.' )) # Update reservation and quantity done for ml in move_lines: rounding = ml.product_uom_id.rounding if float_compare(self.qty_done, 0, precision_rounding=rounding) <= 0: break quantity_to_process = min(self.qty_done, ml.product_uom_qty - ml.qty_done) self.qty_done -= quantity_to_process new_quantity_done = (ml.qty_done + quantity_to_process) # if we produce less than the reserved quantity to produce the finished products # in different lots, # we create different component_move_lines to record which one was used # on which lot of finished product if float_compare(new_quantity_done, ml.product_uom_qty, precision_rounding=rounding) >= 0: ml.write({ 'qty_done': new_quantity_done, 'lot_produced_ids': self._get_produced_lots(), }) else: new_qty_reserved = ml.product_uom_qty - new_quantity_done default = { 'product_uom_qty': new_quantity_done, 'qty_done': new_quantity_done, 'lot_produced_ids': self._get_produced_lots(), } ml.copy(default=default) ml.with_context(bypass_reservation_update=True).write({ 'product_uom_qty': new_qty_reserved, 'qty_done': 0 })
def _generate_consumed_move_line(self, qty_to_add, final_lot, lot=False): if lot: move_lines = self.move_line_ids.filtered(lambda ml: ml.lot_id == lot and not ml.lot_produced_id) else: move_lines = self.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_produced_id) # Sanity check: if the product is a serial number and `lot` is already present in the other # consumed move lines, raise. if lot and self.product_id.tracking == 'serial' and lot in self.move_line_ids.filtered(lambda ml: ml.qty_done).mapped('lot_id'): raise UserError(_('You cannot consume the same serial number twice. Please correct the serial numbers encoded.')) for ml in move_lines: rounding = ml.product_uom_id.rounding if float_compare(qty_to_add, 0, precision_rounding=rounding) <= 0: break quantity_to_process = min(qty_to_add, ml.product_uom_qty - ml.qty_done) qty_to_add -= quantity_to_process new_quantity_done = (ml.qty_done + quantity_to_process) if float_compare(new_quantity_done, ml.product_uom_qty, precision_rounding=rounding) >= 0: ml.write({'qty_done': new_quantity_done, 'lot_produced_id': final_lot.id}) else: new_qty_reserved = ml.product_uom_qty - new_quantity_done default = {'product_uom_qty': new_quantity_done, 'qty_done': new_quantity_done, 'lot_produced_id': final_lot.id} ml.copy(default=default) ml.with_context(bypass_reservation_update=True).write({'product_uom_qty': new_qty_reserved, 'qty_done': 0}) if float_compare(qty_to_add, 0, precision_rounding=self.product_uom.rounding) > 0: # Search for a sub-location where the product is available. This might not be perfectly # correct if the quantity available is spread in several sub-locations, but at least # we should be closer to the reality. Anyway, no reservation is made, so it is still # possible to change it afterwards. quants = self.env['stock.quant']._gather(self.product_id, self.location_id, lot_id=lot, strict=False) available_quantity = self.product_id.uom_id._compute_quantity( self.env['stock.quant']._get_available_quantity( self.product_id, self.location_id, lot_id=lot, strict=False ), self.product_uom ) location_id = False if float_compare(qty_to_add, available_quantity, precision_rounding=self.product_uom.rounding) < 0: location_id = quants.filtered(lambda r: r.quantity > 0)[-1:].location_id vals = { 'move_id': self.id, 'product_id': self.product_id.id, 'location_id': location_id.id if location_id else self.location_id.id, 'production_id': self.raw_material_production_id.id, 'location_dest_id': self.location_dest_id.id, 'product_uom_qty': 0, 'product_uom_id': self.product_uom.id, 'qty_done': qty_to_add, 'lot_produced_id': final_lot.id, } if lot: vals.update({'lot_id': lot.id}) self.env['stock.move.line'].create(vals)
def _check_holidays(self): for holiday in self: if holiday.holiday_type != 'employee' or not holiday.employee_id or holiday.holiday_status_id.allocation_type == 'no': 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 also check the leaves waiting for validation.'))
def _start_nextworkorder(self): rounding = self.product_id.uom_id.rounding if self.next_work_order_id.state == 'pending' and ( (self.operation_id.batch == 'no' and float_compare(self.qty_production, self.qty_produced, precision_rounding=rounding) <= 0) or (self.operation_id.batch == 'yes' and float_compare(self.operation_id.batch_size, self.qty_produced, precision_rounding=rounding) <= 0)): self.next_work_order_id.state = 'ready'
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 _defaults_from_finished_workorder_line(self, reference_lot_lines): for r_line in reference_lot_lines: # see which lot we could suggest and its related qty_producing if not r_line.lot_id: continue candidates = self.finished_workorder_line_ids.filtered( lambda line: line.lot_id == r_line.lot_id) rounding = self.product_uom_id.rounding if not candidates: self.write({ 'finished_lot_id': r_line.lot_id.id, 'qty_producing': r_line.qty_done, }) return True elif float_compare(candidates.qty_done, r_line.qty_done, precision_rounding=rounding) < 0: self.write({ 'finished_lot_id': r_line.lot_id.id, 'qty_producing': r_line.qty_done - candidates.qty_done, }) return True return False
def _compute_is_produced(self): self.is_produced = False for order in self.filtered(lambda p: p.production_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 _compute_outdated(self): grouped_quants = self.env['stock.quant'].read_group( [('product_id', 'in', self.product_id.ids), ('location_id', 'in', self.location_id.ids)], ['product_id', 'location_id', 'lot_id', 'package_id', 'owner_id', 'quantity:sum'], ['product_id', 'location_id', 'lot_id', 'package_id', 'owner_id'], lazy=False) quants = { (quant['product_id'][0], quant['location_id'][0], quant['lot_id'] and quant['lot_id'][0], quant['package_id'] and quant['package_id'][0], quant['owner_id'] and quant['owner_id'][0]): quant['quantity'] for quant in grouped_quants } for line in self: if line.state == 'done' or not line.id: line.outdated = False continue qty = quants.get(( line.product_id.id, line.location_id.id, line.prod_lot_id.id, line.package_id.id, line.partner_id.id, ), 0 ) if float_compare(qty, line.theoretical_qty, precision_rounding=line.product_uom_id.rounding) != 0: line.outdated = True else: line.outdated = False
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 write(self, values): if values.get('order_line') and self.state == 'sale': for order in self: pre_order_line_qty = { order_line: order_line.product_uom_qty for order_line in order.mapped('order_line') } res = super(SaleOrder, self).write(values) if values.get('order_line') and self.state == 'sale': for order in self: to_log = {} for order_line in order.order_line: if pre_order_line_qty.get( order_line, False) and float_compare( order_line.product_uom_qty, pre_order_line_qty[order_line], order_line.product_uom.rounding) < 0: to_log[order_line] = (order_line.product_uom_qty, pre_order_line_qty[order_line]) if to_log: documents = self.env[ 'stock.picking']._log_activity_get_documents( to_log, 'move_ids', 'UP') order._log_decrease_ordered_quantity(documents) return res
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_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 _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 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 _onchange_quantity_context(self): product_qty = False 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 _check_amount_and_confirm_order(self): self.ensure_one() for order in self.sale_order_ids.filtered(lambda so: so.state in ('draft', 'sent')): if float_compare(self.amount, order.amount_total, 2) == 0: order.with_context(send_email=True).action_confirm() else: _logger.warning( '<%s> transaction AMOUNT MISMATCH for order %s (ID %s): expected %r, got %r', self.acquirer_id.provider, order.name, order.id, order.amount_total, self.amount, ) order.message_post( subject=_("Amount Mismatch (%s)") % self.acquirer_id.provider, body= _("The order was not confirmed despite response from the acquirer (%s): order total is %r but acquirer replied with %r." ) % ( self.acquirer_id.provider, order.amount_total, self.amount, ))
def _refresh_wo_lines(self): """ Modify exisiting workorder line in order to match the reservation on stock move line. The strategy is to remove the line that were not processed yet then call _generate_lines_values that recreate workorder line depending the reservation. """ for workorder in self: raw_moves = workorder.move_raw_ids.filtered( lambda move: move.state not in ('done', 'cancel')) wl_to_unlink = self.env['mrp.workorder.line'] for move in raw_moves: rounding = move.product_uom.rounding qty_already_consumed = 0.0 workorder_lines = workorder.raw_workorder_line_ids.filtered( lambda w: w.move_id == move) for wl in workorder_lines: if not wl.qty_done: wl_to_unlink |= wl continue qty_already_consumed += wl.qty_done qty_to_consume = self._prepare_component_quantity( move, workorder.qty_producing) wl_to_unlink.unlink() if float_compare(qty_to_consume, qty_already_consumed, precision_rounding=rounding) > 0: line_values = workorder._generate_lines_values( move, qty_to_consume - qty_already_consumed) self.env['mrp.workorder.line'].create(line_values)
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 UserError(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.virtual_available 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'].sudo().with_context( force_company=company_id).create(moves_values) # Since action_confirm launch following procurement_group we should activate it. moves._action_confirm() return True
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, lang=self.order_id.partner_id.lang or self.env.user.lang or 'en_US') 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 of %s but you only have %s %s available in %s warehouse.') % \ (self.product_uom_qty, self.product_uom.name, self.product_id.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 across all warehouses.\n\n') % \ (self.product_id.virtual_available, product.uom_id.name) for warehouse in self.env['stock.warehouse'].search( []): quantity = self.product_id.with_context( warehouse=warehouse.id).virtual_available if quantity > 0: message += "%s: %s %s\n" % ( warehouse.name, quantity, self.product_id.uom_id.name) warning_mess = { 'title': _('Not enough inventory!'), 'message': message } return {'warning': warning_mess} return {}
def _action_launch_stock_rule(self, previous_product_uom_qty=False): """ Launch procurement group run method with required/custom fields genrated by a sale order line. procurement group will launch '_run_pull', '_run_buy' or '_run_manufacture' depending on the sale order line product rule. """ precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') procurements = [] for line in self: if line.state != 'sale' or not line.product_id.type in ('consu', 'product'): continue qty = line._get_qty_procurement(previous_product_uom_qty) if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0: continue group_id = line._get_procurement_group() if not group_id: group_id = self.env['procurement.group'].create( line._prepare_procurement_group_vals()) 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 line_uom = line.product_uom quant_uom = line.product_id.uom_id product_qty, procurement_uom = line_uom._adjust_uom_quantities( product_qty, quant_uom) procurements.append(self.env['procurement.group'].Procurement( line.product_id, product_qty, procurement_uom, line.order_id.partner_shipping_id.property_stock_customer, line.name, line.order_id.name, line.order_id.company_id, values)) if procurements: self.env['procurement.group'].run(procurements) return True
def _update_line_quantity(self, values): precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') line_products = self.filtered( lambda l: l.product_id.type in ['product', 'consu']) if line_products.mapped('qty_delivered') and float_compare( values['product_uom_qty'], max(line_products.mapped('qty_delivered')), precision_digits=precision) == -1: raise UserError( _('You cannot decrease the ordered quantity below the delivered quantity.\n' 'Create a return first.')) super(SaleOrderLine, self)._update_line_quantity(values)
def _sale_can_be_reinvoice(self): """ determine if the generated analytic line should be reinvoiced or not. For Vendor Bill flow, if the product has a 'erinvoice policy' and is a cost, then we will find the SO on which reinvoice the AAL """ self.ensure_one() uom_precision_digits = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') return float_compare(self.credit or 0.0, self.debit or 0.0, precision_digits=uom_precision_digits ) != 1 and self.product_id.expense_policy not in [ False, 'no' ]
def _compute_allowed_lots_domain(self): """ Check if all the finished products has been assigned to a serial number or a lot in other workorders. If yes, restrict the selectable lot to the lot/sn used in other workorders. """ productions = self.mapped('production_id') treated = self.browse() for production in productions: if production.product_id.tracking == 'none': continue rounding = production.product_uom_id.rounding finished_workorder_lines = production.workorder_ids.mapped( 'finished_workorder_line_ids').filtered( lambda wl: wl.product_id == production.product_id) qties_done_per_lot = defaultdict(list) for finished_workorder_line in finished_workorder_lines: # It is possible to have finished workorder lines without a lot (eg using the dummy # test type). Ignore them when computing the allowed lots. if finished_workorder_line.lot_id: qties_done_per_lot[ finished_workorder_line.lot_id.id].append( finished_workorder_line.qty_done) qty_to_produce = production.product_qty allowed_lot_ids = self.env['stock.production.lot'] qty_produced = sum( [max(qty_dones) for qty_dones in qties_done_per_lot.values()]) if float_compare(qty_produced, qty_to_produce, precision_rounding=rounding) < 0: # If we haven't produced enough, all lots are available allowed_lot_ids = self.env['stock.production.lot'].search([ ('product_id', '=', production.product_id.id), ('company_id', '=', production.company_id.id), ]) else: # If we produced enough, only the already produced lots are available allowed_lot_ids = self.env['stock.production.lot'].browse( qties_done_per_lot.keys()) workorders = production.workorder_ids.filtered( lambda wo: wo.state not in ('done', 'cancel')) for workorder in workorders: if workorder.product_tracking == 'serial': workorder.allowed_lots_domain = allowed_lot_ids - workorder.finished_workorder_line_ids.filtered( lambda wl: wl.product_id == production.product_id ).mapped('lot_id') else: workorder.allowed_lots_domain = allowed_lot_ids treated |= workorder (self - treated).allowed_lots_domain = False
def write(self, values): increased_lines = None decreased_lines = None increased_values = {} decreased_values = {} if 'product_uom_qty' in values: precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') increased_lines = self.sudo().filtered( lambda r: r.product_id.service_to_purchase and r. purchase_line_count and float_compare( r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1) decreased_lines = self.sudo().filtered( lambda r: r.product_id.service_to_purchase and r. purchase_line_count and float_compare( r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == 1) increased_values = { line.id: line.product_uom_qty for line in increased_lines } decreased_values = { line.id: line.product_uom_qty for line in decreased_lines } result = super(SaleOrderLine, self).write(values) if increased_lines: increased_lines._purchase_increase_ordered_qty( values['product_uom_qty'], increased_values) if decreased_lines: decreased_lines._purchase_decrease_ordered_qty( values['product_uom_qty'], decreased_values) return result
def _strict_consumption_check(self): if self.consumption == 'strict': for move in self.move_raw_ids: lines = self._workorder_line_ids().filtered( lambda l: l.move_id == move) qty_done = sum(lines.mapped('qty_done')) qty_to_consume = sum(lines.mapped('qty_to_consume')) rounding = self.product_uom_id.rounding if float_compare(qty_done, qty_to_consume, precision_rounding=rounding) != 0: raise UserError( _('You should consume the quantity of %s defined in the BoM. If you want to consume more or less components, change the consumption setting on the BoM.' ) % lines[0].product_id.name)
def _generate_lot_ids(self): """ Generate stock move lines """ self.ensure_one() MoveLine = self.env['stock.move.line'] tracked_moves = self.move_raw_ids.filtered( lambda move: move.state not in ('done', 'cancel') and move.product_id.tracking != 'none' and move. product_id != self.production_id.product_id and move.bom_line_id) for move in tracked_moves: qty = move.unit_factor * self.qty_producing if move.product_id.tracking == 'serial': while float_compare( qty, 0.0, precision_rounding=move.product_uom.rounding) > 0: MoveLine.create({ 'move_id': move.id, 'product_uom_qty': 0, 'product_uom_id': move.product_uom.id, 'qty_done': min(1, qty), 'production_id': self.production_id.id, 'workorder_id': self.id, 'product_id': move.product_id.id, 'done_wo': False, 'location_id': move.location_id.id, 'location_dest_id': move.location_dest_id.id, }) qty -= 1 else: MoveLine.create({ 'move_id': move.id, 'product_uom_qty': 0, 'product_uom_id': move.product_uom.id, 'qty_done': qty, 'product_id': move.product_id.id, 'production_id': self.production_id.id, 'workorder_id': self.id, 'done_wo': False, 'location_id': move.location_id.id, 'location_dest_id': move.location_dest_id.id, })
def _onchange_qty_done(self): """ When the user is encoding a produce line for a tracked product, we apply some logic to help him. This onchange will warn him if he set `qty_done` to a non-supported value. """ res = {} if self.product_id.tracking == 'serial' and self.qty_done: if float_compare(self.qty_done, 1.0, precision_rounding=self.move_id.product_id.uom_id. rounding) != 0: message = _( 'You can only process 1.0 %s of products with unique serial number.' ) % self.product_id.uom_id.name res['warning'] = {'title': _('Warning'), 'message': message} return res
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
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.user.company_id.currency_id.decimal_places 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 _reservation_is_updatable(self, quantity, reserved_quant): self.ensure_one() if self.lot_produced_ids: 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 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') location_id = self.location_id if self.picking_id and self.picking_id.picking_type_code == 'incoming': location_id = self.picking_id.location_dest_id available_qty = sum(self.env['stock.quant']._gather( self.product_id, 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: ctx = dict(self.env.context) ctx.update({ 'default_product_id': self.product_id.id, 'default_location_id': self.location_id.id, 'default_scrap_id': self.id }) return { 'name': _('Insufficient Quantity'), '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': ctx, 'target': 'new' }