Ejemplo n.º 1
0
    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
                })
Ejemplo n.º 2
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)
Ejemplo n.º 3
0
 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.'))
Ejemplo n.º 4
0
 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'
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
 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
Ejemplo n.º 7
0
 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
Ejemplo n.º 8
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
Ejemplo n.º 9
0
 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'
         }
Ejemplo n.º 10
0
 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
Ejemplo n.º 11
0
 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'}
Ejemplo n.º 12
0
 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)
         )
Ejemplo n.º 13
0
 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
Ejemplo n.º 14
0
    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
Ejemplo n.º 15
0
 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,
                   ))
Ejemplo n.º 16
0
    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)
Ejemplo n.º 17
0
    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
Ejemplo n.º 18
0
 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 {}
Ejemplo n.º 19
0
    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
Ejemplo n.º 20
0
 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)
Ejemplo n.º 21
0
 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'
                          ]
Ejemplo n.º 22
0
    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
Ejemplo n.º 23
0
    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
Ejemplo n.º 24
0
 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)
Ejemplo n.º 25
0
 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,
             })
Ejemplo n.º 26
0
 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
Ejemplo n.º 27
0
 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
Ejemplo n.º 28
0
    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
Ejemplo n.º 29
0
 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)
Ejemplo n.º 30
0
 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'
         }