Пример #1
0
 def _compute_duration_display(self):
     for allocation in self:
         allocation.duration_display = '%g %s' % (
             (float_round(allocation.number_of_hours_display,
                          precision_digits=2)
              if allocation.type_request_unit == 'hour' else float_round(
                  allocation.number_of_days_display, precision_digits=2)),
             _('hours')
             if allocation.type_request_unit == 'hour' else _('days'))
Пример #2
0
    def value_to_html(self, value, options):
        if 'decimal_precision' in options:
            precision = self.env['decimal.precision'].search([
                ('name', '=', options['decimal_precision'])
            ]).digits
        else:
            precision = options['precision']

        if precision is None:
            fmt = '%f'
        else:
            value = float_utils.float_round(value, precision_digits=precision)
            fmt = '%.{precision}f'.format(precision=precision)

        formatted = self.user_lang().format(fmt, value, grouping=True).replace(
            r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}')

        # %f does not strip trailing zeroes. %g does but its precision causes
        # it to switch to scientific notation starting at a million *and* to
        # strip decimals. So use %f and if no precision was specified manually
        # strip trailing 0.
        if precision is None:
            formatted = re.sub(r'(?:(0|\d+?)0+)$', r'\1', formatted)

        return pycompat.to_text(formatted)
Пример #3
0
    def stripe_form_generate_values(self, tx_values):
        self.ensure_one()

        base_url = self.get_base_url()
        stripe_session_data = {
            "payment_method_types[]":
            "card",
            "line_items[][amount]":
            int(tx_values["amount"] if tx_values["currency"].name in
                INT_CURRENCIES else float_round(tx_values["amount"] * 100, 2)),
            "line_items[][currency]":
            tx_values["currency"].name,
            "line_items[][quantity]":
            1,
            "line_items[][name]":
            tx_values["reference"],
            "client_reference_id":
            tx_values["reference"],
            "success_url":
            urls.url_join(base_url, StripeController._success_url) +
            "?reference=%s" % urls.url_quote_plus(tx_values["reference"]),
            "cancel_url":
            urls.url_join(base_url, StripeController._cancel_url) +
            "?reference=%s" % urls.url_quote_plus(tx_values["reference"]),
            "payment_intent_data[description]":
            tx_values["reference"],
            "customer_email":
            tx_values.get("partner_email")
            or tx_values.get("billing_partner_email"),
        }
        tx_values["session_id"] = self._create_stripe_session(
            stripe_session_data)

        return tx_values
Пример #4
0
    def _create_stripe_charge(self,
                              acquirer_ref=None,
                              tokenid=None,
                              email=None):
        api_url_charge = 'https://%s/charges' % (
            self.acquirer_id._get_stripe_api_url())
        charge_params = {
            'amount':
            int(self.amount if self.currency_id.name in
                INT_CURRENCIES else float_round(self.amount * 100, 2)),
            'currency':
            self.currency_id.name,
            'metadata[reference]':
            self.reference,
            'description':
            self.reference,
        }
        if acquirer_ref:
            charge_params['customer'] = acquirer_ref
        if tokenid:
            charge_params['card'] = str(tokenid)
        if email:
            charge_params['receipt_email'] = email.strip()

        _logger.info(
            '_create_stripe_charge: Sending values to URL %s, values:\n%s',
            api_url_charge, pprint.pformat(charge_params))
        r = requests.post(api_url_charge,
                          auth=(self.acquirer_id.stripe_secret_key, ''),
                          params=charge_params,
                          headers=STRIPE_HEADERS)
        res = r.json()
        _logger.info('_create_stripe_charge: Values received:\n%s',
                     pprint.pformat(res))
        return res
Пример #5
0
 def _compute_purchased_product_qty(self):
     for template in self:
         template.purchased_product_qty = float_round(
             sum([
                 p.purchased_product_qty
                 for p in template.product_variant_ids
             ]),
             precision_rounding=template.uom_id.rounding)
Пример #6
0
 def _compute_sales_count(self):
     for product in self:
         product.sales_count = float_round(
             sum([
                 p.sales_count for p in product.with_context(
                     active_test=False).product_variant_ids
             ]),
             precision_rounding=product.uom_id.rounding)
Пример #7
0
 def _get_margin_value(self, value, previous_value=0.0):
     margin = 0.0
     if (value != previous_value) and (value != 0.0
                                       and previous_value != 0.0):
         margin = float_round(
             (float(value - previous_value) / previous_value or 1) * 100,
             precision_digits=2)
     return margin
Пример #8
0
    def get_opening_move_differences(self, opening_move_lines):
        currency = self.currency_id
        balancing_move_line = opening_move_lines.filtered(
            lambda x: x.account_id == self.get_unaffected_earnings_account())

        debits_sum = credits_sum = 0.0
        for line in opening_move_lines:
            if line != balancing_move_line:
                #skip the autobalancing move line
                debits_sum += line.debit
                credits_sum += line.credit

        difference = abs(debits_sum - credits_sum)
        debit_diff = (debits_sum > credits_sum) and float_round(
            difference, precision_rounding=currency.rounding) or 0.0
        credit_diff = (debits_sum < credits_sum) and float_round(
            difference, precision_rounding=currency.rounding) or 0.0
        return debit_diff, credit_diff
Пример #9
0
 def ogone_form_generate_values(self, values):
     base_url = self.get_base_url()
     ogone_tx_values = dict(values)
     param_plus = {'return_url': ogone_tx_values.pop('return_url', False)}
     temp_ogone_tx_values = {
         'PSPID':
         self.ogone_pspid,
         'ORDERID':
         values['reference'],
         'AMOUNT':
         float_repr(float_round(values['amount'], 2) * 100, 0),
         'CURRENCY':
         values['currency'] and values['currency'].name or '',
         'LANGUAGE':
         values.get('partner_lang'),
         'CN':
         values.get('partner_name'),
         'EMAIL':
         values.get('partner_email'),
         'OWNERZIP':
         values.get('partner_zip'),
         'OWNERADDRESS':
         values.get('partner_address'),
         'OWNERTOWN':
         values.get('partner_city'),
         'OWNERCTY':
         values.get('partner_country')
         and values.get('partner_country').code or '',
         'OWNERTELNO':
         values.get('partner_phone'),
         'ACCEPTURL':
         urls.url_join(base_url, OgoneController._accept_url),
         'DECLINEURL':
         urls.url_join(base_url, OgoneController._decline_url),
         'EXCEPTIONURL':
         urls.url_join(base_url, OgoneController._exception_url),
         'CANCELURL':
         urls.url_join(base_url, OgoneController._cancel_url),
         'PARAMPLUS':
         url_encode(param_plus),
     }
     if self.save_token in ['ask', 'always']:
         temp_ogone_tx_values.update({
             'ALIAS':
             'ODOO-NEW-ALIAS-%s' % time.time(),  # something unique,
             'ALIASUSAGE':
             values.get('alias_usage') or self.ogone_alias_usage,
         })
     shasign = self._ogone_generate_shasign('in', temp_ogone_tx_values)
     temp_ogone_tx_values['SHASIGN'] = shasign
     ogone_tx_values.update(temp_ogone_tx_values)
     return ogone_tx_values
Пример #10
0
 def name_get(self):
     if not self._context.get('employee_id'):
         # leave counts is based on employee_id, would be inaccurate if not based on correct employee
         return super(HolidaysType, self).name_get()
     res = []
     for record in self:
         name = record.name
         if record.allocation_type != 'no':
             name = "%(name)s (%(count)s)" % {
                 'name':
                 name,
                 'count':
                 _('%g remaining out of %g') % (
                     float_round(record.virtual_remaining_leaves,
                                 precision_digits=2) or 0.0,
                     float_round(record.max_leaves, precision_digits=2)
                     or 0.0,
                 ) + (_(' hours')
                      if record.request_unit == 'hour' else _(' days'))
             }
         res.append((record.id, name))
     return res
Пример #11
0
 def _compute_leaves_count(self):
     all_leaves = self.env['hr.leave.report'].read_group(
         [('employee_id', 'in', self.ids),
          ('holiday_status_id.allocation_type', '!=', 'no'),
          ('holiday_status_id.active', '=', 'True'),
          ('state', '=', 'validate')],
         fields=['number_of_days', 'employee_id'],
         groupby=['employee_id'])
     mapping = dict([(leave['employee_id'][0], leave['number_of_days'])
                     for leave in all_leaves])
     for employee in self:
         employee.leaves_count = float_round(mapping.get(employee.id, 0),
                                             precision_digits=2)
Пример #12
0
    def default_get(self, fields):
        if len(self.env.context.get('active_ids', list())) > 1:
            raise UserError(_("You may only return one picking at a time."))
        res = super(ReturnPicking, self).default_get(fields)

        move_dest_exists = False
        product_return_moves = []
        picking = self.env['stock.picking'].browse(self.env.context.get('active_id'))
        if picking:
            res.update({'picking_id': picking.id})
            if picking.state != 'done':
                raise UserError(_("You may only return Done pickings."))
            # In case we want to set specific default values (e.g. 'to_refund'), we must fetch the
            # default values for creation.
            line_fields = [f for f in self.env['stock.return.picking.line']._fields.keys()]
            product_return_moves_data_tmpl = self.env['stock.return.picking.line'].default_get(line_fields)
            for move in picking.move_lines:
                if move.state == 'cancel':
                    continue
                if move.scrapped:
                    continue
                if move.move_dest_ids:
                    move_dest_exists = True
                quantity = move.product_qty - sum(move.move_dest_ids.filtered(lambda m: m.state in ['partially_available', 'assigned', 'done']).\
                                                  mapped('move_line_ids').mapped('product_qty'))
                quantity = float_round(quantity, precision_rounding=move.product_uom.rounding)
                product_return_moves_data = dict(product_return_moves_data_tmpl)
                product_return_moves_data.update({
                    'product_id': move.product_id.id,
                    'quantity': quantity,
                    'move_id': move.id,
                    'uom_id': move.product_id.uom_id.id,
                })
                product_return_moves.append((0, 0, product_return_moves_data))

            if not product_return_moves:
                raise UserError(_("No products to return (only lines in Done state and not fully returned yet can be returned)."))
            if 'product_return_moves' in fields:
                res.update({'product_return_moves': product_return_moves})
            if 'move_dest_exists' in fields:
                res.update({'move_dest_exists': move_dest_exists})
            if 'parent_location_id' in fields and picking.location_id.usage == 'internal':
                res.update({'parent_location_id': picking.picking_type_id.warehouse_id and picking.picking_type_id.warehouse_id.view_location_id.id or picking.location_id.location_id.id})
            if 'original_location_id' in fields:
                res.update({'original_location_id': picking.location_id.id})
            if 'location_id' in fields:
                location_id = picking.location_id.id
                if picking.picking_type_id.return_picking_type_id.default_location_dest_id.return_location:
                    location_id = picking.picking_type_id.return_picking_type_id.default_location_dest_id.id
                res['location_id'] = location_id
        return res
Пример #13
0
 def _compute_mrp_product_qty(self):
     date_from = fields.Datetime.to_string(fields.datetime.now() -
                                           timedelta(days=365))
     #TODO: state = done?
     domain = [('state', '=', 'done'), ('product_id', 'in', self.ids),
               ('date_planned_start', '>', date_from)]
     read_group_res = self.env['mrp.production'].read_group(
         domain, ['product_id', 'product_uom_qty'], ['product_id'])
     mapped_data = dict([(data['product_id'][0], data['product_uom_qty'])
                         for data in read_group_res])
     for product in self:
         product.mrp_product_qty = float_round(
             mapped_data.get(product.id, 0),
             precision_rounding=product.uom_id.rounding)
Пример #14
0
 def _compute_purchased_product_qty(self):
     date_from = fields.Datetime.to_string(fields.datetime.now() -
                                           timedelta(days=365))
     domain = [('state', 'in', ['purchase', 'done']),
               ('product_id', 'in', self.mapped('id')),
               ('date_order', '>', date_from)]
     PurchaseOrderLines = self.env['purchase.order.line'].search(domain)
     order_lines = self.env['purchase.order.line'].read_group(
         domain, ['product_id', 'product_uom_qty'], ['product_id'])
     purchased_data = dict([(data['product_id'][0], data['product_uom_qty'])
                            for data in order_lines])
     for product in self:
         product.purchased_product_qty = float_round(
             purchased_data.get(product.id, 0),
             precision_rounding=product.uom_id.rounding)
Пример #15
0
    def _stripe_create_payment_intent(self, acquirer_ref=None, email=None):
        if self.stripe_payment_intent:
            _logger.info(
                "_stripe_create_payment_intent: trying to create an intent when one already exists (tx #%s), refetching values for intent %s",
                self.id, self.stripe_payment_intent)
            res = self.acquirer_id._stripe_request("payment_intents/%s" %
                                                   self.stripe_payment_intent,
                                                   method="GET")
            _logger.info("_stripe_create_payment_intent: Values received:\n%s",
                         pprint.pformat(res))
            return res
        if not self.payment_token_id.stripe_payment_method:
            # old token before installing stripe_sca, need to fetch data from the api
            self.payment_token_id._stripe_sca_migrate_customer()
        charge_params = {
            "amount":
            int(self.amount if self.currency_id.name in
                INT_CURRENCIES else float_round(self.amount * 100, 2)),
            "currency":
            self.currency_id.name.lower(),
            "confirm":
            True,
            "off_session":
            True,
            "payment_method":
            self.payment_token_id.stripe_payment_method,
            "customer":
            self.payment_token_id.acquirer_ref,
            "description":
            self.reference,
        }
        if not self.env.context.get('off_session'):
            charge_params.update(setup_future_usage='off_session',
                                 off_session=False)
        _logger.info(
            "_stripe_create_payment_intent: Sending values to stripe, values:\n%s",
            pprint.pformat(charge_params),
        )

        res = self.acquirer_id._stripe_request("payment_intents",
                                               charge_params)
        if res.get("charges") and res.get("charges").get("total_count"):
            res = res.get("charges").get("data")[0]

        _logger.info("_stripe_create_payment_intent: Values received:\n%s",
                     pprint.pformat(res))
        return res
Пример #16
0
    def _create_stripe_refund(self):
        refund_params = {
            "charge": self.acquirer_reference,
            "amount": int(
                float_round(self.amount * 100, 2)
            ),  # by default, stripe refund the full amount (we don't really need to specify the value)
            "metadata[reference]": self.reference,
        }

        _logger.info(
            "_create_stripe_refund: Sending values to stripe URL, values:\n%s",
            pprint.pformat(refund_params),
        )
        res = self.acquirer_id._stripe_request("refunds", refund_params)
        _logger.info("_create_stripe_refund: Values received:\n%s",
                     pprint.pformat(res))
        return res
Пример #17
0
 def _stripe_form_get_invalid_parameters(self, data):
     invalid_parameters = []
     if data.get("amount") != int(
             self.amount if self.currency_id.name in
             INT_CURRENCIES else float_round(self.amount * 100, 2)):
         invalid_parameters.append(
             ("Amount", data.get("amount"), self.amount * 100))
     if data.get("currency").upper() != self.currency_id.name:
         invalid_parameters.append(
             ("Currency", data.get("currency"), self.currency_id.name))
     if (data.get("payment_intent")
             and data.get("payment_intent") != self.stripe_payment_intent):
         invalid_parameters.append((
             "Payment Intent",
             data.get("payment_intent"),
             self.stripe_payment_intent,
         ))
     return invalid_parameters
Пример #18
0
    def _compute_sales_count(self):
        r = {}
        if not self.user_has_groups('sales_team.group_sale_salesman'):
            return r
        date_from = fields.Datetime.to_string(fields.datetime.combine(fields.datetime.now() - timedelta(days=365),
                                                                      time.min))

        done_states = self.env['sale.report']._get_done_states()

        domain = [
            ('state', 'in', done_states),
            ('product_id', 'in', self.ids),
            ('date', '>=', date_from),
        ]
        for group in self.env['sale.report'].read_group(domain, ['product_id', 'product_uom_qty'], ['product_id']):
            r[group['product_id'][0]] = group['product_uom_qty']
        for product in self:
            product.sales_count = float_round(r.get(product.id, 0), precision_rounding=product.uom_id.rounding)
        return r
Пример #19
0
    def _create_stripe_refund(self):
        api_url_refund = 'https://%s/refunds' % (
            self.acquirer_id._get_stripe_api_url())

        refund_params = {
            'charge': self.acquirer_reference,
            'amount': int(
                float_round(self.amount * 100, 2)
            ),  # by default, stripe refund the full amount (we don't really need to specify the value)
            'metadata[reference]': self.reference,
        }

        _logger.info(
            '_create_stripe_refund: Sending values to URL %s, values:\n%s',
            api_url_refund, pprint.pformat(refund_params))
        r = requests.post(api_url_refund,
                          auth=(self.acquirer_id.stripe_secret_key, ''),
                          params=refund_params,
                          headers=STRIPE_HEADERS)
        res = r.json()
        _logger.info('_create_stripe_refund: Values received:\n%s',
                     pprint.pformat(res))
        return res
Пример #20
0
    def _compute_quantities_dict(self,
                                 lot_id,
                                 owner_id,
                                 package_id,
                                 from_date=False,
                                 to_date=False):
        domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self._get_domain_locations(
        )
        domain_quant = [('product_id', 'in', self.ids)] + domain_quant_loc
        dates_in_the_past = False
        # only to_date as to_date will correspond to qty_available
        to_date = fields.Datetime.to_datetime(to_date)
        if to_date and to_date < fields.Datetime.now():
            dates_in_the_past = True

        domain_move_in = [('product_id', 'in', self.ids)] + domain_move_in_loc
        domain_move_out = [('product_id', 'in', self.ids)
                           ] + domain_move_out_loc
        if lot_id is not None:
            domain_quant += [('lot_id', '=', lot_id)]
        if owner_id is not None:
            domain_quant += [('owner_id', '=', owner_id)]
            domain_move_in += [('restrict_partner_id', '=', owner_id)]
            domain_move_out += [('restrict_partner_id', '=', owner_id)]
        if package_id is not None:
            domain_quant += [('package_id', '=', package_id)]
        if dates_in_the_past:
            domain_move_in_done = list(domain_move_in)
            domain_move_out_done = list(domain_move_out)
        if from_date:
            domain_move_in += [('date', '>=', from_date)]
            domain_move_out += [('date', '>=', from_date)]
        if to_date:
            domain_move_in += [('date', '<=', to_date)]
            domain_move_out += [('date', '<=', to_date)]

        Move = self.env['stock.move']
        Quant = self.env['stock.quant']
        domain_move_in_todo = [
            ('state', 'in',
             ('waiting', 'confirmed', 'assigned', 'partially_available'))
        ] + domain_move_in
        domain_move_out_todo = [
            ('state', 'in',
             ('waiting', 'confirmed', 'assigned', 'partially_available'))
        ] + domain_move_out
        moves_in_res = dict((item['product_id'][0], item['product_qty'])
                            for item in Move.read_group(
                                domain_move_in_todo,
                                ['product_id', 'product_qty'], ['product_id'],
                                orderby='id'))
        moves_out_res = dict((item['product_id'][0], item['product_qty'])
                             for item in Move.read_group(
                                 domain_move_out_todo,
                                 ['product_id', 'product_qty'], ['product_id'],
                                 orderby='id'))
        quants_res = dict(
            (item['product_id'][0], item['quantity'])
            for item in Quant.read_group(
                domain_quant, ['product_id', 'quantity'], ['product_id'],
                orderby='id'))
        if dates_in_the_past:
            # Calculate the moves that were done before now to calculate back in time (as most questions will be recent ones)
            domain_move_in_done = [('state', '=', 'done'),
                                   ('date', '>', to_date)
                                   ] + domain_move_in_done
            domain_move_out_done = [('state', '=', 'done'),
                                    ('date', '>', to_date)
                                    ] + domain_move_out_done
            moves_in_res_past = dict(
                (item['product_id'][0], item['product_qty']) for item in
                Move.read_group(domain_move_in_done,
                                ['product_id', 'product_qty'], ['product_id'],
                                orderby='id'))
            moves_out_res_past = dict(
                (item['product_id'][0], item['product_qty']) for item in
                Move.read_group(domain_move_out_done,
                                ['product_id', 'product_qty'], ['product_id'],
                                orderby='id'))

        res = dict()
        for product in self.with_context(prefetch_fields=False):
            product_id = product.id
            rounding = product.uom_id.rounding
            res[product_id] = {}
            if dates_in_the_past:
                qty_available = quants_res.get(
                    product_id, 0.0) - moves_in_res_past.get(
                        product_id, 0.0) + moves_out_res_past.get(
                            product_id, 0.0)
            else:
                qty_available = quants_res.get(product_id, 0.0)
            res[product_id]['qty_available'] = float_round(
                qty_available, precision_rounding=rounding)
            res[product_id]['incoming_qty'] = float_round(
                moves_in_res.get(product_id, 0.0), precision_rounding=rounding)
            res[product_id]['outgoing_qty'] = float_round(
                moves_out_res.get(product_id, 0.0),
                precision_rounding=rounding)
            res[product_id]['virtual_available'] = float_round(
                qty_available + res[product_id]['incoming_qty'] -
                res[product_id]['outgoing_qty'],
                precision_rounding=rounding)

        return res
Пример #21
0
 def _compute_remaining_leaves(self):
     remaining = self._get_remaining_leaves()
     for employee in self:
         employee.remaining_leaves = float_round(remaining.get(
             employee.id, 0.0),
                                                 precision_digits=2)
Пример #22
0
 def _compute_mrp_product_qty(self):
     self.mrp_product_qty = float_round(
         sum(self.mapped('product_variant_ids').mapped('mrp_product_qty')),
         precision_rounding=self.uom_id.rounding)
Пример #23
0
    def test_01_compute_price_operation_cost(self):
        """Test calcuation of bom cost with operations."""
        workcenter_from1 = Form(self.env['mrp.workcenter'])
        workcenter_from1.name = 'Workcenter'
        workcenter_from1.time_efficiency = 100
        workcenter_from1.capacity = 2
        workcenter_from1.oee_target = 100
        workcenter_from1.time_start = 0
        workcenter_from1.time_stop = 0
        workcenter_from1.costs_hour = 100
        workcenter_1 = workcenter_from1.save()

        routing_form1 = Form(self.Routing)
        routing_form1.name = 'Assembly Furniture'
        routing_1 = routing_form1.save()

        operation_1 = self.operation.create({
            'name': 'Cutting',
            'workcenter_id': workcenter_1.id,
            'routing_id': routing_1.id,
            'time_mode': 'manual',
            'time_cycle_manual': 20,
            'batch': 'no',
            'sequence': 1,
        })
        operation_2 = self.operation.create({
            'name': 'Drilling',
            'workcenter_id': workcenter_1.id,
            'routing_id': routing_1.id,
            'time_mode': 'manual',
            'time_cycle_manual': 25,
            'batch': 'no',
            'sequence': 2,
        })
        operation_3 = self.operation.create({
            'name': 'Fitting',
            'workcenter_id': workcenter_1.id,
            'routing_id': routing_1.id,
            'time_mode': 'manual',
            'time_cycle_manual': 30,
            'batch': 'no',
            'sequence': 3,
        })

        # -----------------------------------------------------------------
        # Dinning Table Operation Cost(1 Unit)
        # -----------------------------------------------------------------
        # Operation cost calculate for 1 units
        # Cutting        (20 / 60) * 100 =  33.33
        # Drilling       (25 / 60) * 100 =  41.67
        # Fitting        (30 / 60) * 100 =  50.00
        # ----------------------------------------
        # Operation Cost  1 unit = 125
        # -----------------------------------------------------------------

        self.bom_1.routing_id = routing_1.id

        # --------------------------------------------------------------------------
        # Table Head Operation Cost (1 Dozen)
        # --------------------------------------------------------------------------
        # Operation cost calculate for 1 dozens
        # Cutting        (20 * 1 / 60) * 100 =  33,33
        # Drilling       (25 * 1 / 60) * 100 =  41,67
        # Fitting        (30 * 1 / 60) * 100 =  50
        # ----------------------------------------
        # Operation Cost 1 dozen (125 per dozen) and 10.42 for 1 Unit
        # --------------------------------------------------------------------------

        self.bom_2.routing_id = routing_1.id

        self.assertEqual(self.dining_table.standard_price, 1000,
                         "Initial price of the Product should be 1000")
        self.dining_table.button_bom_cost()
        # Total cost of Dining Table = (550) + Total cost of operations (125) = 675.0
        self.assertEquals(
            float_round(self.dining_table.standard_price, precision_digits=2),
            675.0, "After computing price from BoM price should be 612.5")
        self.Product.browse([self.dining_table.id,
                             self.table_head.id]).action_bom_cost()
        # Total cost of Dining Table = (718.75) + Total cost of all operations (125 + 10.42) = 854.17
        self.assertEquals(
            float_compare(self.dining_table.standard_price,
                          854.17,
                          precision_digits=2), 0,
            "After computing price from BoM price should be 786.46")
Пример #24
0
    def _free_reservation(self,
                          product_id,
                          location_id,
                          quantity,
                          lot_id=None,
                          package_id=None,
                          owner_id=None,
                          ml_to_ignore=None):
        """ When editing a done move line or validating one with some forced quantities, it is
        possible to impact quants that were not reserved. It is therefore necessary to edit or
        unlink the move lines that reserved a quantity now unavailable.

        :param ml_to_ignore: recordset of `stock.move.line` that should NOT be unreserved
        """
        self.ensure_one()

        if ml_to_ignore is None:
            ml_to_ignore = self.env['stock.move.line']
        ml_to_ignore |= self

        # Check the available quantity, with the `strict` kw set to `True`. If the available
        # quantity is greather than the quantity now unavailable, there is nothing to do.
        available_quantity = self.env['stock.quant']._get_available_quantity(
            product_id,
            location_id,
            lot_id=lot_id,
            package_id=package_id,
            owner_id=owner_id,
            strict=True)
        if quantity > available_quantity:
            # We now have to find the move lines that reserved our now unavailable quantity. We
            # take care to exclude ourselves and the move lines were work had already been done.
            outdated_move_lines_domain = [
                ('state', 'not in', ['done', 'cancel']),
                ('product_id', '=', product_id.id),
                ('lot_id', '=', lot_id.id if lot_id else False),
                ('location_id', '=', location_id.id),
                ('owner_id', '=', owner_id.id if owner_id else False),
                ('package_id', '=', package_id.id if package_id else False),
                ('product_qty', '>', 0.0),
                ('id', 'not in', ml_to_ignore.ids),
            ]
            # We take the current picking first, then the pickings with the latest scheduled date
            current_picking_first = lambda cand: (
                cand.picking_id != self.move_id.picking_id,
                -(cand.picking_id.scheduled_date or cand.move_id.date_expected)
                .timestamp() if cand.picking_id or cand.move_id else -cand.id,
            )
            outdated_candidates = self.env['stock.move.line'].search(
                outdated_move_lines_domain).sorted(current_picking_first)

            # As the move's state is not computed over the move lines, we'll have to manually
            # recompute the moves which we adapted their lines.
            move_to_recompute_state = self.env['stock.move']

            rounding = self.product_uom_id.rounding
            for candidate in outdated_candidates:
                if float_compare(candidate.product_qty,
                                 quantity,
                                 precision_rounding=rounding) <= 0:
                    quantity -= candidate.product_qty
                    move_to_recompute_state |= candidate.move_id
                    if candidate.qty_done:
                        candidate.product_uom_qty = 0.0
                    else:
                        candidate.unlink()
                    if float_is_zero(quantity, precision_rounding=rounding):
                        break
                else:
                    # split this move line and assign the new part to our extra move
                    quantity_split = float_round(
                        candidate.product_qty - quantity,
                        precision_rounding=self.product_uom_id.rounding,
                        rounding_method='UP')
                    candidate.product_uom_qty = self.product_id.uom_id._compute_quantity(
                        quantity_split,
                        candidate.product_uom_id,
                        rounding_method='HALF-UP')
                    move_to_recompute_state |= candidate.move_id
                    break
            move_to_recompute_state._recompute_state()
Пример #25
0
    def _action_done(self):
        """ This method is called during a move's `action_done`. It'll actually move a quant from
        the source location to the destination location, and unreserve if needed in the source
        location.

        This method is intended to be called on all the move lines of a move. This method is not
        intended to be called when editing a `done` move (that's what the override of `write` here
        is done.
        """
        Quant = self.env['stock.quant']

        # First, we loop over all the move lines to do a preliminary check: `qty_done` should not
        # be negative and, according to the presence of a picking type or a linked inventory
        # adjustment, enforce some rules on the `lot_id` field. If `qty_done` is null, we unlink
        # the line. It is mandatory in order to free the reservation and correctly apply
        # `action_done` on the next move lines.
        ml_to_delete = self.env['stock.move.line']
        for ml in self:
            # Check here if `ml.qty_done` respects the rounding of `ml.product_uom_id`.
            uom_qty = float_round(
                ml.qty_done,
                precision_rounding=ml.product_uom_id.rounding,
                rounding_method='HALF-UP')
            precision_digits = self.env['decimal.precision'].precision_get(
                'Product Unit of Measure')
            qty_done = float_round(ml.qty_done,
                                   precision_digits=precision_digits,
                                   rounding_method='HALF-UP')
            if float_compare(
                    uom_qty, qty_done, precision_digits=precision_digits) != 0:
                raise UserError(
                    _('The quantity done for the product "%s" doesn\'t respect the rounding precision \
                                  defined on the unit of measure "%s". Please change the quantity done or the \
                                  rounding precision of your unit of measure.')
                    % (ml.product_id.display_name, ml.product_uom_id.name))

            qty_done_float_compared = float_compare(
                ml.qty_done, 0, precision_rounding=ml.product_uom_id.rounding)
            if qty_done_float_compared > 0:
                if ml.product_id.tracking != 'none':
                    picking_type_id = ml.move_id.picking_type_id
                    if picking_type_id:
                        if picking_type_id.use_create_lots:
                            # If a picking type is linked, we may have to create a production lot on
                            # the fly before assigning it to the move line if the user checked both
                            # `use_create_lots` and `use_existing_lots`.
                            if ml.lot_name and not ml.lot_id:
                                lot = self.env['stock.production.lot'].create({
                                    'name':
                                    ml.lot_name,
                                    'product_id':
                                    ml.product_id.id
                                })
                                ml.write({'lot_id': lot.id})
                        elif not picking_type_id.use_create_lots and not picking_type_id.use_existing_lots:
                            # If the user disabled both `use_create_lots` and `use_existing_lots`
                            # checkboxes on the picking type, he's allowed to enter tracked
                            # products without a `lot_id`.
                            continue
                    elif ml.move_id.inventory_id:
                        # If an inventory adjustment is linked, the user is allowed to enter
                        # tracked products without a `lot_id`.
                        continue

                    if not ml.lot_id:
                        raise UserError(
                            _('You need to supply a Lot/Serial number for product %s.'
                              ) % ml.product_id.display_name)
            elif qty_done_float_compared < 0:
                raise UserError(_('No negative quantities allowed'))
            else:
                ml_to_delete |= ml
        ml_to_delete.unlink()

        # Now, we can actually move the quant.
        done_ml = self.env['stock.move.line']
        for ml in self - ml_to_delete:
            if ml.product_id.type == 'product':
                rounding = ml.product_uom_id.rounding

                # if this move line is force assigned, unreserve elsewhere if needed
                if not ml.location_id.should_bypass_reservation(
                ) and float_compare(ml.qty_done,
                                    ml.product_uom_qty,
                                    precision_rounding=rounding) > 0:
                    qty_done_product_uom = ml.product_uom_id._compute_quantity(
                        ml.qty_done,
                        ml.product_id.uom_id,
                        rounding_method='HALF-UP')
                    extra_qty = qty_done_product_uom - ml.product_qty
                    ml._free_reservation(ml.product_id,
                                         ml.location_id,
                                         extra_qty,
                                         lot_id=ml.lot_id,
                                         package_id=ml.package_id,
                                         owner_id=ml.owner_id,
                                         ml_to_ignore=done_ml)
                # unreserve what's been reserved
                if not ml.location_id.should_bypass_reservation(
                ) and ml.product_id.type == 'product' and ml.product_qty:
                    Quant._update_reserved_quantity(ml.product_id,
                                                    ml.location_id,
                                                    -ml.product_qty,
                                                    lot_id=ml.lot_id,
                                                    package_id=ml.package_id,
                                                    owner_id=ml.owner_id,
                                                    strict=True)

                # move what's been actually done
                quantity = ml.product_uom_id._compute_quantity(
                    ml.qty_done,
                    ml.move_id.product_id.uom_id,
                    rounding_method='HALF-UP')
                available_qty, in_date = Quant._update_available_quantity(
                    ml.product_id,
                    ml.location_id,
                    -quantity,
                    lot_id=ml.lot_id,
                    package_id=ml.package_id,
                    owner_id=ml.owner_id)
                if available_qty < 0 and ml.lot_id:
                    # see if we can compensate the negative quants with some untracked quants
                    untracked_qty = Quant._get_available_quantity(
                        ml.product_id,
                        ml.location_id,
                        lot_id=False,
                        package_id=ml.package_id,
                        owner_id=ml.owner_id,
                        strict=True)
                    if untracked_qty:
                        taken_from_untracked_qty = min(untracked_qty,
                                                       abs(quantity))
                        Quant._update_available_quantity(
                            ml.product_id,
                            ml.location_id,
                            -taken_from_untracked_qty,
                            lot_id=False,
                            package_id=ml.package_id,
                            owner_id=ml.owner_id)
                        Quant._update_available_quantity(
                            ml.product_id,
                            ml.location_id,
                            taken_from_untracked_qty,
                            lot_id=ml.lot_id,
                            package_id=ml.package_id,
                            owner_id=ml.owner_id)
                Quant._update_available_quantity(
                    ml.product_id,
                    ml.location_dest_id,
                    quantity,
                    lot_id=ml.lot_id,
                    package_id=ml.result_package_id,
                    owner_id=ml.owner_id,
                    in_date=in_date)
            done_ml |= ml
        # Reset the reserved quantity as we just moved it to the destination location.
        (self -
         ml_to_delete).with_context(bypass_reservation_update=True).write({
             'product_uom_qty':
             0.00,
             'date':
             fields.Datetime.now(),
         })