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 _create_extra_move_lines(self):
        """Create new sml if quantity produced is bigger than the reserved one"""
        vals_list = []
        quants = self.env['stock.quant']._gather(self.product_id,
                                                 self.move_id.location_id,
                                                 lot_id=self.lot_id,
                                                 strict=False)
        # Search for a sub-locations where the product is available.
        # Loop on the quants to get the locations. If there is not enough
        # quantity into stock, we take the move location. Anyway, no
        # reservation is made, so it is still possible to change it afterwards.
        for quant in quants:
            quantity = quant.quantity - quant.reserved_quantity
            rounding = quant.product_uom_id.rounding
            if (float_compare(quant.quantity, 0, precision_rounding=rounding)
                    <= 0 or float_compare(
                        quantity, 0, precision_rounding=rounding) <= 0):
                continue
            vals = {
                'move_id': self.move_id.id,
                'product_id': self.product_id.id,
                'location_id': quant.location_id.id,
                'location_dest_id': self.move_id.location_dest_id.id,
                'product_uom_qty': 0,
                'product_uom_id': quant.product_uom_id.id,
                'qty_done': min(quantity, self.qty_done),
                'lot_produced_ids': self._get_produced_lots(),
            }
            if self.lot_id:
                vals.update({'lot_id': self.lot_id.id})

            vals_list.append(vals)
            self.qty_done -= vals['qty_done']
            # If all the qty_done is distributed, we can close the loop
            if float_compare(
                    self.qty_done,
                    0,
                    precision_rounding=self.product_uom_id.rounding) <= 0:
                break

        if float_compare(self.qty_done,
                         0,
                         precision_rounding=self.product_uom_id.rounding) > 0:
            vals = {
                'move_id': self.move_id.id,
                'product_id': self.product_id.id,
                'location_id': self.move_id.location_id.id,
                'location_dest_id': self.move_id.location_dest_id.id,
                'product_uom_qty': 0,
                'product_uom_id': self.product_uom_id.id,
                'qty_done': self.qty_done,
                'lot_produced_ids': self._get_produced_lots(),
            }
            if self.lot_id:
                vals.update({'lot_id': self.lot_id.id})

            vals_list.append(vals)

        return vals_list
Ejemplo n.º 3
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.º 4
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.º 5
0
 def action_validate(self):
     self.ensure_one()
     if self.filtered(lambda repair: any(op.product_uom_qty < 0 for op in repair.operations)):
         raise UserError(_("You can not enter negative quantities."))
     if self.product_id.type == 'consu':
         return self.action_repair_confirm()
     precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
     available_qty_owner = self.env['stock.quant']._get_available_quantity(self.product_id, self.location_id, self.lot_id, owner_id=self.partner_id, strict=True)
     available_qty_noown = self.env['stock.quant']._get_available_quantity(self.product_id, self.location_id, self.lot_id, strict=True)
     for available_qty in [available_qty_owner, available_qty_noown]:
         if float_compare(available_qty, self.product_qty, precision_digits=precision) >= 0:
             return self.action_repair_confirm()
     else:
         return {
             'name': _('Insufficient Quantity'),
             'view_mode': 'form',
             'res_model': 'stock.warn.insufficient.qty.repair',
             'view_id': self.env.ref('repair.stock_warn_insufficient_qty_repair_form_view').id,
             'type': 'ir.actions.act_window',
             'context': {
                 'default_product_id': self.product_id.id,
                 'default_location_id': self.location_id.id,
                 'default_repair_id': self.id
                 },
             'target': 'new'
         }
Ejemplo n.º 6
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.º 7
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.º 8
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.º 9
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') if not order_line.is_expense}

        if values.get('partner_shipping_id'):
            new_partner = self.env['res.partner'].browse(values.get('partner_shipping_id'))
            for record in self:
                picking = record.mapped('picking_ids').filtered(lambda x: x.state not in ('done', 'cancel'))
                addresses = (record.partner_shipping_id.display_name, new_partner.display_name)
                message = _("""The delivery address has been changed on the Sales Order<br/>
                        From <strong>"%s"</strong> To <strong>"%s"</strong>,
                        You should probably update the partner on this document.""") % addresses
                picking.activity_schedule('mail.mail_activity_data_warning', note=message, user_id=self.env.user.id)

        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 float_compare(order_line.product_uom_qty, pre_order_line_qty.get(order_line, 0.0), order_line.product_uom.rounding) < 0:
                        to_log[order_line] = (order_line.product_uom_qty, pre_order_line_qty.get(order_line, 0.0))
                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.º 10
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.º 11
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.º 12
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.º 13
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.º 14
0
 def write(self, values):
     lines = self.env['sale.order.line']
     if 'product_uom_qty' in values:
         precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
         lines = self.filtered(
             lambda r: r.state == 'sale' and not r.is_expense and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1)
     previous_product_uom_qty = {line.id: line.product_uom_qty for line in lines}
     res = super(SaleOrderLine, self).write(values)
     if lines:
         lines._action_launch_stock_rule(previous_product_uom_qty)
     return res
Ejemplo n.º 15
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_is_zero(0.0, precision_rounding=0.0)

        with self.assertRaises(AssertionError):
            float_is_zero(0.0, precision_rounding=-0.1)

        with self.assertRaises(AssertionError):
            float_compare(0.01,
                          0.02,
                          precision_digits=3,
                          precision_rounding=0.01)

        with self.assertRaises(AssertionError):
            float_compare(1.0, 1.0, precision_rounding=0.0)

        with self.assertRaises(AssertionError):
            float_compare(1.0, 1.0, precision_rounding=-0.1)

        with self.assertRaises(AssertionError):
            float_round(0.01, precision_digits=3, precision_rounding=0.01)

        with self.assertRaises(AssertionError):
            float_round(1.25, precision_rounding=0.0)

        with self.assertRaises(AssertionError):
            float_round(1.25, precision_rounding=-0.1)
Ejemplo n.º 16
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.º 17
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.º 18
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.º 19
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.º 20
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.º 21
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 not float_is_zero(
             self.qty_done, self.product_uom_id.rounding):
         if float_compare(
                 self.qty_done,
                 1.0,
                 precision_rounding=self.product_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.º 22
0
    def _record_production(self):
        # Check all the product_produce line have a move id (the user can add product
        # to consume directly in the wizard)
        for line in self._workorder_line_ids():
            if not line.move_id:
                # Find move_id that would match
                if line.raw_product_produce_id:
                    moves = self.move_raw_ids
                else:
                    moves = self.move_finished_ids
                move_id = moves.filtered(lambda m: m.product_id == line.product_id and m.state not in ('done', 'cancel'))
                if not move_id:
                    # create a move to assign it to the line
                    if line.raw_product_produce_id:
                        values = {
                            'name': self.production_id.name,
                            'reference': self.production_id.name,
                            'product_id': line.product_id.id,
                            'product_uom': line.product_uom_id.id,
                            'location_id': self.production_id.location_src_id.id,
                            'location_dest_id': line.product_id.property_stock_production.id,
                            'raw_material_production_id': self.production_id.id,
                            'group_id': self.production_id.procurement_group_id.id,
                            'origin': self.production_id.name,
                            'state': 'confirmed',
                            'company_id': self.production_id.company_id.id,
                        }
                    else:
                        values = self.production_id._get_finished_move_value(line.product_id.id, 0, line.product_uom_id.id)
                    move_id = self.env['stock.move'].create(values)
                line.move_id = move_id.id

        # because of an ORM limitation (fields on transient models are not
        # recomputed by updates in non-transient models), the related fields on
        # this model are not recomputed by the creations above
        self.invalidate_cache(['move_raw_ids', 'move_finished_ids'])

        # Save product produce lines data into stock moves/move lines
        quantity = self.qty_producing
        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)
        self._update_finished_move()
        self._update_moves()
        if self.production_id.state == 'confirmed':
            self.production_id.write({
                'date_start': datetime.now(),
            })
Ejemplo n.º 23
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._get_cash_basis_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.º 24
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'
         }
Ejemplo n.º 25
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.º 26
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.º 27
0
    def _select_seller(self,
                       partner_id=False,
                       quantity=0.0,
                       date=None,
                       uom_id=False,
                       params=False):
        self.ensure_one()
        if date is None:
            date = fields.Date.context_today(self)
        precision = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')

        res = self.env['product.supplierinfo']
        sellers = self._prepare_sellers(params)
        if self.env.context.get('force_company'):
            sellers = sellers.filtered(
                lambda s: not s.company_id or s.company_id.id == self.env.
                context['force_company'])
        for seller in sellers:
            # Set quantity in UoM of seller
            quantity_uom_seller = quantity
            if quantity_uom_seller and uom_id and uom_id != seller.product_uom:
                quantity_uom_seller = uom_id._compute_quantity(
                    quantity_uom_seller, seller.product_uom)

            if seller.date_start and seller.date_start > date:
                continue
            if seller.date_end and seller.date_end < date:
                continue
            if partner_id and seller.name not in [
                    partner_id, partner_id.parent_id
            ]:
                continue
            if float_compare(quantity_uom_seller,
                             seller.min_qty,
                             precision_digits=precision) == -1:
                continue
            if seller.product_id and seller.product_id != self:
                continue
            if not res or res.name == seller.name:
                res |= seller
        return res.sorted('price')[:1]
Ejemplo n.º 28
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_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.º 29
0
    def _update_moves(self):
        """ Once the production is done. Modify the workorder lines into
        stock move line with the registered lot and quantity done.
        """
        # Before writting produce quantities, we ensure they respect the bom strictness
        self._strict_consumption_check()
        vals_list = []
        workorder_lines_to_process = self._workorder_line_ids().filtered(
            lambda line: line.product_id != self.product_id and line.qty_done >
            0)
        for line in workorder_lines_to_process:
            line._update_move_lines()
            if float_compare(
                    line.qty_done,
                    0,
                    precision_rounding=line.product_uom_id.rounding) > 0:
                vals_list += line._create_extra_move_lines()

        self._workorder_line_ids().filtered(
            lambda line: line.product_id != self.product_id).unlink()
        self.env['stock.move.line'].create(vals_list)
Ejemplo n.º 30
0
    def test_sale_with_taxes(self):
        """ Test SO with taxes applied on its lines and check subtotal applied on its lines and total applied on the SO """
        # Create a tax with price included
        tax_include = self.env['account.tax'].create({
            'name': 'Tax with price include',
            'amount': 10,
            'price_include': True
        })
        # Create a tax with price not included
        tax_exclude = self.env['account.tax'].create({
            'name': 'Tax with no price include',
            'amount': 10,
        })

        # Apply taxes on the sale order lines
        self.sol_product_order.write({'tax_id': [(4, tax_include.id)]})
        self.sol_serv_deliver.write({'tax_id': [(4, tax_include.id)]})
        self.sol_serv_order.write({'tax_id': [(4, tax_exclude.id)]})
        self.sol_product_deliver.write({'tax_id': [(4, tax_exclude.id)]})

        # Trigger onchange to reset discount, unit price, subtotal, ...
        for line in self.sale_order.order_line:
            line.product_id_change()
            line._onchange_discount()

        for line in self.sale_order.order_line:
            if line.tax_id.price_include:
                price = line.price_unit * line.product_uom_qty - line.price_tax
            else:
                price = line.price_unit * line.product_uom_qty

            self.assertEquals(
                float_compare(line.price_subtotal, price, precision_digits=2),
                0)

        self.assertEquals(
            self.sale_order.amount_total,
            self.sale_order.amount_untaxed + self.sale_order.amount_tax,
            'Taxes should be applied')