Beispiel #1
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)
Beispiel #2
0
 def check_finished_move_lots(self):
     """ Handle by product tracked """
     by_product_moves = self.production_id.move_finished_ids.filtered(
         lambda m: m.product_id != self.product_id and m.product_id.tracking
         != 'none' and m.state not in ('done', 'cancel'))
     for by_product_move in by_product_moves:
         rounding = by_product_move.product_uom.rounding
         quantity = float_round(self.product_qty *
                                by_product_move.unit_factor,
                                precision_rounding=rounding)
         values = {
             'move_id': by_product_move.id,
             'product_id': by_product_move.product_id.id,
             'production_id': self.production_id.id,
             'product_uom_id': by_product_move.product_uom.id,
             'location_id': by_product_move.location_id.id,
             'location_dest_id': by_product_move.location_dest_id.id,
         }
         if by_product_move.product_id.tracking == 'lot':
             values.update({
                 'product_uom_qty': quantity,
                 'qty_done': quantity,
             })
             self.env['stock.move.line'].create(values)
         else:
             values.update({
                 'product_uom_qty': 1.0,
                 'qty_done': 1.0,
             })
             for i in range(0, int(quantity)):
                 self.env['stock.move.line'].create(values)
     return super(MrpProductProduce, self).check_finished_move_lots()
Beispiel #3
0
    def create(self, values):
        if values.get('partner_id'):  # @TDENOTE: not sure
            values.update(
                self.on_change_partner_id(values['partner_id'])['value'])

        # call custom create method if defined (i.e. ogone_create for ogone)
        if values.get('acquirer_id'):
            acquirer = self.env['payment.acquirer'].browse(
                values['acquirer_id'])

            # compute fees
            custom_method_name = '%s_compute_fees' % acquirer.provider
            if hasattr(acquirer, custom_method_name):
                fees = getattr(acquirer, custom_method_name)(
                    values.get('amount', 0.0), values.get('currency_id'),
                    values.get('partner_country_id'))
                values['fees'] = float_round(fees, 2)

            # custom create
            custom_method_name = '%s_create' % acquirer.provider
            if hasattr(acquirer, custom_method_name):
                values.update(getattr(self, custom_method_name)(values))

        # Default value of reference is
        tx = super(PaymentTransaction, self).create(values)
        if not values.get('reference'):
            tx.write({'reference': str(tx.id)})

        # Generate callback hash if it is configured on the tx; avoid generating unnecessary stuff
        # (limited sudo env for checking callback presence, must work for manual transactions too)
        tx_sudo = tx.sudo()
        if tx_sudo.callback_model_id and tx_sudo.callback_res_id and tx_sudo.callback_method:
            tx.write({'callback_hash': tx._generate_callback_hash()})

        return tx
Beispiel #4
0
 def _get_price(self, pricelist, product, qty):
     sale_price_digits = self.env['decimal.precision'].precision_get(
         'Product Price')
     price = pricelist.get_product_price(product, qty, False)
     if not price:
         price = product.list_price
     return float_round(price, precision_digits=sale_price_digits)
Beispiel #5
0
 def try_round(amount, expected, digits=3, method='HALF-UP'):
     value = float_round(amount,
                         precision_digits=digits,
                         rounding_method=method)
     result = float_repr(value, precision_digits=digits)
     self.assertEqual(
         result, expected,
         'Rounding error: got %s, expected %s' % (result, expected))
    def round(self, amount):
        """Compute the rounding on the amount passed as parameter.

        :param amount: the amount to round
        :return: the rounded amount depending the rounding value and the rounding method
        """
        return float_round(amount,
                           precision_rounding=self.rounding,
                           rounding_method=self.rounding_method)
Beispiel #7
0
    def adyen_form_generate_values(self, values):
        base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
        # tmp
        import datetime
        from dateutil import relativedelta

        if self.provider == 'adyen' and len(self.adyen_skin_hmac_key) == 64:
            tmp_date = datetime.datetime.today() + relativedelta.relativedelta(days=1)

            values.update({
                'merchantReference': values['reference'],
                'paymentAmount': '%d' % int(tools.float_round(values['amount'], 2) * 100),
                'currencyCode': values['currency'] and values['currency'].name or '',
                'shipBeforeDate': tmp_date.strftime('%Y-%m-%d'),
                'skinCode': self.adyen_skin_code,
                'merchantAccount': self.adyen_merchant_account,
                'shopperLocale': values.get('partner_lang', ''),
                'sessionValidity': tmp_date.isoformat('T')[:19] + "Z",
                'resURL': urls.url_join(base_url, AdyenController._return_url),
                'merchantReturnData': json.dumps({'return_url': '%s' % values.pop('return_url')}) if values.get('return_url', '') else False,
                'shopperEmail': values.get('partner_email', ''),
            })
            values['merchantSig'] = self._adyen_generate_merchant_sig_sha256('in', values)

        else:
            tmp_date = datetime.date.today() + relativedelta.relativedelta(days=1)

            values.update({
                'merchantReference': values['reference'],
                'paymentAmount': '%d' % int(tools.float_round(values['amount'], 2) * 100),
                'currencyCode': values['currency'] and values['currency'].name or '',
                'shipBeforeDate': tmp_date,
                'skinCode': self.adyen_skin_code,
                'merchantAccount': self.adyen_merchant_account,
                'shopperLocale': values.get('partner_lang'),
                'sessionValidity': tmp_date,
                'resURL': urls.url_join(base_url, AdyenController._return_url),
                'merchantReturnData': json.dumps({'return_url': '%s' % values.pop('return_url')}) if values.get('return_url') else False,
            })
            values['merchantSig'] = self._adyen_generate_merchant_sig('in', values)

        return values
Beispiel #8
0
    def round(self, amount):
        """Return ``amount`` rounded  according to ``self``'s rounding rules.

           :param float amount: the amount to round
           :return: rounded float
        """
        # TODO: Need to check why it calls round() from sale.py, _amount_all() with *No* ID after below commits,
        # https://github.com/odoo/odoo/commit/36ee1ad813204dcb91e9f5f20d746dff6f080ac2
        # https://github.com/odoo/odoo/commit/0b6058c585d7d9a57bd7581b8211f20fca3ec3f7
        # Removing self.ensure_one() will make few test cases to break of modules event_sale, sale_mrp and stock_dropshipping.
        #self.ensure_one()
        return tools.float_round(amount, precision_rounding=self.rounding)
Beispiel #9
0
 def _compute_quantity(self,
                       qty,
                       to_unit,
                       round=True,
                       rounding_method='UP'):
     if not self:
         return qty
     self.ensure_one()
     if self.category_id.id != to_unit.category_id.id:
         if self._context.get('raise-exception', True):
             raise UserError(
                 _('Conversion from Product UoM %s to Default UoM %s is not possible as they both belong to different Category!.'
                   ) % (self.name, to_unit.name))
         else:
             return qty
     amount = qty / self.factor
     if to_unit:
         amount = amount * to_unit.factor
         if round:
             amount = tools.float_round(amount,
                                        precision_rounding=to_unit.rounding,
                                        rounding_method=rounding_method)
     return amount
Beispiel #10
0
 def write(self, values):
     if ('acquirer_id' in values
             or 'amount' in values) and 'fees' not in values:
         # The acquirer or the amount has changed, and the fees are not explicitly forced. Fees must be recomputed.
         acquirer = None
         if values.get('acquirer_id'):
             acquirer = self.env['payment.acquirer'].browse(
                 values['acquirer_id'])
         for tx in self:
             vals = dict(values, fees=0.0)
             if not acquirer:
                 acquirer = tx.acquirer_id
             custom_method_name = '%s_compute_fees' % acquirer.provider
             # TDE FIXME: shouldn't we use fee_implemented ?
             if hasattr(acquirer, custom_method_name):
                 fees = getattr(acquirer, custom_method_name)(
                     (values['amount'] if 'amount' in values else tx.amount)
                     or 0.0, values.get('currency_id') or tx.currency_id.id,
                     values.get('partner_country_id')
                     or tx.partner_country_id.id)
                 vals['fees'] = float_round(fees, 2)
             res = super(PaymentTransaction, tx).write(vals)
         return res
     return super(PaymentTransaction, self).write(values)
Beispiel #11
0
    def _run_valuation(self, quantity=None):
        self.ensure_one()
        if self._is_in():
            valued_move_lines = self.move_line_ids.filtered(
                lambda ml: not ml.location_id._should_be_valued() and ml.
                location_dest_id._should_be_valued() and not ml.owner_id)
            valued_quantity = 0
            for valued_move_line in valued_move_lines:
                valued_quantity += valued_move_line.product_uom_id._compute_quantity(
                    valued_move_line.qty_done, self.product_id.uom_id)

            # Note: we always compute the fifo `remaining_value` and `remaining_qty` fields no
            # matter which cost method is set, to ease the switching of cost method.
            vals = {}
            price_unit = self._get_price_unit()
            value = price_unit * (quantity or valued_quantity)
            vals = {
                'price_unit':
                price_unit,
                'value':
                value if quantity is None or not self.value else self.value,
                'remaining_value':
                value if quantity is None else self.remaining_value + value,
            }
            vals[
                'remaining_qty'] = valued_quantity if quantity is None else self.remaining_qty + quantity

            if self.product_id.cost_method == 'standard':
                value = self.product_id.standard_price * (quantity
                                                          or valued_quantity)
                vals.update({
                    'price_unit':
                    self.product_id.standard_price,
                    'value':
                    value
                    if quantity is None or not self.value else self.value,
                })
            self.write(vals)
        elif self._is_out():
            valued_move_lines = self.move_line_ids.filtered(
                lambda ml: ml.location_id._should_be_valued() and not ml.
                location_dest_id._should_be_valued() and not ml.owner_id)
            valued_quantity = sum(valued_move_lines.mapped('qty_done'))
            self.env['stock.move']._run_fifo(self, quantity=quantity)
            if self.product_id.cost_method in ['standard', 'average']:
                curr_rounding = self.company_id.currency_id.rounding
                value = -float_round(
                    self.product_id.standard_price *
                    (valued_quantity if quantity is None else quantity),
                    precision_rounding=curr_rounding)
                self.write({
                    'value':
                    value if quantity is None else self.value + value,
                    'price_unit':
                    value / valued_quantity,
                })
        elif self._is_dropshipped():
            curr_rounding = self.company_id.currency_id.rounding
            if self.product_id.cost_method in ['fifo']:
                price_unit = self._get_price_unit()
                # see test_dropship_fifo_perpetual_anglosaxon_ordered
                self.product_id.standard_price = price_unit
            else:
                price_unit = self.product_id.standard_price
            value = float_round(self.product_qty * price_unit,
                                precision_rounding=curr_rounding)
            # In move have a positive value, out move have a negative value, let's arbitrary say
            # dropship are positive.
            self.write({
                'value': value,
                'price_unit': price_unit,
            })
Beispiel #12
0
    def _compute_price_rule(self,
                            products_qty_partner,
                            date=False,
                            uom_id=False):
        """ Low-level method - Mono pricelist, multi products
        Returns: dict{product_id: (price, suitable_rule) for the given pricelist}

        If date in context: Date of the pricelist (%Y-%m-%d)

            :param products_qty_partner: list of typles products, quantity, partner
            :param datetime date: validity date
            :param ID uom_id: intermediate unit of measure
        """
        self.ensure_one()
        if not date:
            date = self._context.get('date') or fields.Date.context_today(self)
        if not uom_id and self._context.get('uom'):
            uom_id = self._context['uom']
        if uom_id:
            # rebrowse with uom if given
            products = [
                item[0].with_context(uom=uom_id)
                for item in products_qty_partner
            ]
            products_qty_partner = [
                (products[index], data_struct[1], data_struct[2])
                for index, data_struct in enumerate(products_qty_partner)
            ]
        else:
            products = [item[0] for item in products_qty_partner]

        if not products:
            return {}

        categ_ids = {}
        for p in products:
            categ = p.categ_id
            while categ:
                categ_ids[categ.id] = True
                categ = categ.parent_id
        categ_ids = list(categ_ids)

        is_product_template = products[0]._name == "product.template"
        if is_product_template:
            prod_tmpl_ids = [tmpl.id for tmpl in products]
            # all variants of all products
            prod_ids = [
                p.id for p in list(
                    chain.from_iterable(
                        [t.product_variant_ids for t in products]))
            ]
        else:
            prod_ids = [product.id for product in products]
            prod_tmpl_ids = [
                product.product_tmpl_id.id for product in products
            ]

        # Load all rules
        self._cr.execute(
            'SELECT item.id '
            'FROM product_pricelist_item AS item '
            'LEFT JOIN product_category AS categ '
            'ON item.categ_id = categ.id '
            'WHERE (item.product_tmpl_id IS NULL OR item.product_tmpl_id = any(%s))'
            'AND (item.product_id IS NULL OR item.product_id = any(%s))'
            'AND (item.categ_id IS NULL OR item.categ_id = any(%s)) '
            'AND (item.pricelist_id = %s) '
            'AND (item.date_start IS NULL OR item.date_start<=%s) '
            'AND (item.date_end IS NULL OR item.date_end>=%s)'
            'ORDER BY item.applied_on, item.min_quantity desc, categ.parent_left desc',
            (prod_tmpl_ids, prod_ids, categ_ids, self.id, date, date))

        item_ids = [x[0] for x in self._cr.fetchall()]
        items = self.env['product.pricelist.item'].browse(item_ids)
        results = {}
        for product, qty, partner in products_qty_partner:
            results[product.id] = 0.0
            suitable_rule = False

            # Final unit price is computed according to `qty` in the `qty_uom_id` UoM.
            # An intermediary unit price may be computed according to a different UoM, in
            # which case the price_uom_id contains that UoM.
            # The final price will be converted to match `qty_uom_id`.
            qty_uom_id = self._context.get('uom') or product.uom_id.id
            price_uom_id = product.uom_id.id
            qty_in_product_uom = qty
            if qty_uom_id != product.uom_id.id:
                try:
                    qty_in_product_uom = self.env['product.uom'].browse([
                        self._context['uom']
                    ])._compute_quantity(qty, product.uom_id)
                except UserError:
                    # Ignored - incompatible UoM in context, use default product UoM
                    pass

            # if Public user try to access standard price from website sale, need to call price_compute.
            # TDE SURPRISE: product can actually be a template
            price = product.price_compute('list_price')[product.id]

            price_uom = self.env['product.uom'].browse([qty_uom_id])
            for rule in items:
                if rule.min_quantity and qty_in_product_uom < rule.min_quantity:
                    continue
                if is_product_template:
                    if rule.product_tmpl_id and product.id != rule.product_tmpl_id.id:
                        continue
                    if rule.product_id and not (
                            product.product_variant_count == 1
                            and product.product_variant_id.id
                            == rule.product_id.id):
                        # product rule acceptable on template if has only one variant
                        continue
                else:
                    if rule.product_tmpl_id and product.product_tmpl_id.id != rule.product_tmpl_id.id:
                        continue
                    if rule.product_id and product.id != rule.product_id.id:
                        continue

                if rule.categ_id:
                    cat = product.categ_id
                    while cat:
                        if cat.id == rule.categ_id.id:
                            break
                        cat = cat.parent_id
                    if not cat:
                        continue

                if rule.base == 'pricelist' and rule.base_pricelist_id:
                    price_tmp = rule.base_pricelist_id._compute_price_rule([
                        (product, qty, partner)
                    ])[product.id][0]  # TDE: 0 = price, 1 = rule
                    price = rule.base_pricelist_id.currency_id.compute(
                        price_tmp, self.currency_id, round=False)
                else:
                    # if base option is public price take sale price else cost price of product
                    # price_compute returns the price in the context UoM, i.e. qty_uom_id
                    price = product.price_compute(rule.base)[product.id]

                convert_to_price_uom = (lambda price: product.uom_id.
                                        _compute_price(price, price_uom))

                if price is not False:
                    if rule.compute_price == 'fixed':
                        price = convert_to_price_uom(rule.fixed_price)
                    elif rule.compute_price == 'percentage':
                        price = (price - (price *
                                          (rule.percent_price / 100))) or 0.0
                    else:
                        # complete formula
                        price_limit = price
                        price = (price - (price *
                                          (rule.price_discount / 100))) or 0.0
                        if rule.price_round:
                            price = tools.float_round(
                                price, precision_rounding=rule.price_round)

                        if rule.price_surcharge:
                            price_surcharge = convert_to_price_uom(
                                rule.price_surcharge)
                            price += price_surcharge

                        if rule.price_min_margin:
                            price_min_margin = convert_to_price_uom(
                                rule.price_min_margin)
                            price = max(price, price_limit + price_min_margin)

                        if rule.price_max_margin:
                            price_max_margin = convert_to_price_uom(
                                rule.price_max_margin)
                            price = min(price, price_limit + price_max_margin)
                    suitable_rule = rule
                break
            # Final price conversion into pricelist currency
            if suitable_rule and suitable_rule.compute_price != 'fixed' and suitable_rule.base != 'pricelist':
                price = product.currency_id.compute(price,
                                                    self.currency_id,
                                                    round=False)

            results[product.id] = (price, suitable_rule and suitable_rule.id
                                   or False)

        return results
Beispiel #13
0
    def _procure_orderpoint_confirm(self,
                                    use_new_cursor=False,
                                    company_id=False):
        """ Create procurements based on orderpoints.
        :param bool use_new_cursor: if set, use a dedicated cursor and auto-commit after processing
            1000 orderpoints.
            This is appropriate for batch jobs only.
        """
        if company_id and self.env.user.company_id.id != company_id:
            # To ensure that the company_id is taken into account for
            # all the processes triggered by this method
            # i.e. If a PO is generated by the run of the procurements the
            # sequence to use is the one for the specified company not the
            # one of the user's company
            self = self.with_context(company_id=company_id,
                                     force_company=company_id)
        OrderPoint = self.env['stock.warehouse.orderpoint']
        domain = self._get_orderpoint_domain(company_id=company_id)
        orderpoints_noprefetch = OrderPoint.with_context(
            prefetch_fields=False).search(
                domain,
                order=self._procurement_from_orderpoint_get_order()).ids
        while orderpoints_noprefetch:
            if use_new_cursor:
                cr = registry(self._cr.dbname).cursor()
                self = self.with_env(self.env(cr=cr))
            OrderPoint = self.env['stock.warehouse.orderpoint']

            orderpoints = OrderPoint.browse(orderpoints_noprefetch[:1000])
            orderpoints_noprefetch = orderpoints_noprefetch[1000:]

            # Calculate groups that can be executed together
            location_data = defaultdict(lambda: dict(
                products=self.env['product.product'],
                orderpoints=self.env['stock.warehouse.orderpoint'],
                groups=list()))
            for orderpoint in orderpoints:
                key = self._procurement_from_orderpoint_get_grouping_key(
                    [orderpoint.id])
                location_data[key]['products'] += orderpoint.product_id
                location_data[key]['orderpoints'] += orderpoint
                location_data[key][
                    'groups'] = self._procurement_from_orderpoint_get_groups(
                        [orderpoint.id])

            for location_id, location_data in location_data.items():
                location_orderpoints = location_data['orderpoints']
                product_context = dict(
                    self._context,
                    location=location_orderpoints[0].location_id.id)
                substract_quantity = location_orderpoints._quantity_in_progress(
                )

                for group in location_data['groups']:
                    if group.get('from_date'):
                        product_context['from_date'] = group[
                            'from_date'].strftime(
                                DEFAULT_SERVER_DATETIME_FORMAT)
                    if group['to_date']:
                        product_context['to_date'] = group['to_date'].strftime(
                            DEFAULT_SERVER_DATETIME_FORMAT)
                    product_quantity = location_data['products'].with_context(
                        product_context)._product_available()
                    for orderpoint in location_orderpoints:
                        try:
                            op_product_virtual = product_quantity[
                                orderpoint.product_id.id]['virtual_available']
                            if op_product_virtual is None:
                                continue
                            if float_compare(op_product_virtual,
                                             orderpoint.product_min_qty,
                                             precision_rounding=orderpoint.
                                             product_uom.rounding) <= 0:
                                qty = max(orderpoint.product_min_qty,
                                          orderpoint.product_max_qty
                                          ) - op_product_virtual
                                remainder = orderpoint.qty_multiple > 0 and qty % orderpoint.qty_multiple or 0.0

                                if float_compare(remainder,
                                                 0.0,
                                                 precision_rounding=orderpoint.
                                                 product_uom.rounding) > 0:
                                    qty += orderpoint.qty_multiple - remainder

                                if float_compare(qty,
                                                 0.0,
                                                 precision_rounding=orderpoint.
                                                 product_uom.rounding) < 0:
                                    continue

                                qty -= substract_quantity[orderpoint.id]
                                qty_rounded = float_round(
                                    qty,
                                    precision_rounding=orderpoint.product_uom.
                                    rounding)
                                if qty_rounded > 0:
                                    values = orderpoint._prepare_procurement_values(
                                        qty_rounded,
                                        **group['procurement_values'])
                                    try:
                                        with self._cr.savepoint():
                                            self.env['procurement.group'].run(
                                                orderpoint.product_id,
                                                qty_rounded,
                                                orderpoint.product_uom,
                                                orderpoint.location_id,
                                                orderpoint.name,
                                                orderpoint.name, values)
                                    except UserError as error:
                                        self.env[
                                            'procurement.rule']._log_next_activity(
                                                orderpoint.product_id,
                                                error.name)
                                    self._procurement_from_orderpoint_post_process(
                                        [orderpoint.id])
                                if use_new_cursor:
                                    cr.commit()

                        except OperationalError:
                            if use_new_cursor:
                                orderpoints_noprefetch += [orderpoint.id]
                                cr.rollback()
                                continue
                            else:
                                raise

            try:
                if use_new_cursor:
                    cr.commit()
            except OperationalError:
                if use_new_cursor:
                    cr.rollback()
                    continue
                else:
                    raise

            if use_new_cursor:
                cr.commit()
                cr.close()

        return {}
Beispiel #14
0
    def _prepare_plan_values(self, domain):

        timesheet_lines = request.env['account.analytic.line'].search(domain)
        currency = request.env.user.company_id.currency_id

        values = {
            'currency': currency,
            'timesheet_lines': timesheet_lines,
            'domain': domain,
        }
        hour_rounding = request.env.ref('product.product_uom_hour').rounding
        billable_types = [
            'non_billable', 'non_billable_project', 'billable_time',
            'billable_fixed'
        ]

        # -- Stat Buttons
        values['stat_buttons'] = self._plan_get_stat_button(timesheet_lines)

        # -- Dashboard (per billable type)
        dashboard_values = {
            'hours': dict.fromkeys(billable_types + ['total'], 0.0),
            'rates': dict.fromkeys(billable_types + ['total'], 0.0),
            'money_amount': {
                'invoiced': 0.0,
                'to_invoiced': 0.0,
                'cost': 0.0,
                'total': 0.0,
            }
        }
        dashboard_domain = domain + [('timesheet_invoice_type', '!=', False)
                                     ]  # force billable type
        dashboard_data = request.env['account.analytic.line'].read_group(
            dashboard_domain,
            ['unit_amount', 'timesheet_revenue', 'timesheet_invoice_type'],
            ['timesheet_invoice_type'])

        dashboard_total_hours = sum(
            [data['unit_amount'] for data in dashboard_data])
        for data in dashboard_data:
            billable_type = data['timesheet_invoice_type']
            # hours
            dashboard_values['hours'][billable_type] = float_round(
                data.get('unit_amount'), precision_rounding=hour_rounding)
            dashboard_values['hours']['total'] += float_round(
                data.get('unit_amount'), precision_rounding=hour_rounding)
            # rates
            dashboard_values['rates'][billable_type] = round(
                data.get('unit_amount') / dashboard_total_hours * 100, 2)
            dashboard_values['rates']['total'] += round(
                data.get('unit_amount') / dashboard_total_hours * 100, 2)

        # money_amount
        so_lines = values['timesheet_lines'].mapped('so_line')
        invoice_lines = so_lines.mapped('invoice_lines')
        dashboard_values['money_amount']['invoiced'] = sum([
            inv_line.currency_id.with_context(
                date=inv_line.invoice_id.date_invoice).compute(
                    inv_line.price_unit * inv_line.quantity, currency)
            for inv_line in invoice_lines.filtered(
                lambda line: line.invoice_id.state in ['open', 'paid'])
        ])
        dashboard_values['money_amount']['to_invoice'] = sum([
            sol.currency_id.compute(
                sol.price_unit *
                (1 -
                 (sol.discount or 0.0) / 100.0) * sol.qty_to_invoice, currency)
            for sol in so_lines
        ]) + sum([
            i.currency_id.with_context(date=i.invoice_id.date_invoice).compute(
                i.price_unit * i.quantity, currency)
            for i in invoice_lines.filtered(
                lambda line: line.invoice_id.state == 'draft')
        ])
        dashboard_values['money_amount']['cost'] = sum(
            values['timesheet_lines'].mapped('amount'))
        dashboard_values['money_amount']['total'] = sum([
            dashboard_values['money_amount'][item]
            for item in dashboard_values['money_amount'].keys()
        ])

        values['dashboard'] = dashboard_values

        # -- Time Repartition (per employee)
        repartition_domain = domain + [('employee_id', '!=', False),
                                       ('timesheet_invoice_type', '!=', False)
                                       ]  # force billable type
        repartition_data = request.env['account.analytic.line'].read_group(
            repartition_domain,
            ['employee_id', 'timesheet_invoice_type', 'unit_amount'],
            ['employee_id', 'timesheet_invoice_type'],
            lazy=False)

        # set repartition per type per employee
        repartition_employee = {}
        for data in repartition_data:
            employee_id = data['employee_id'][0]
            repartition_employee.setdefault(
                employee_id,
                dict(
                    employee_id=data['employee_id'][0],
                    employee_name=data['employee_id'][1],
                    non_billable_project=0.0,
                    non_billable=0.0,
                    billable_time=0.0,
                    billable_fixed=0.0,
                    total=0.0,
                ))[data['timesheet_invoice_type']] = float_round(
                    data.get('unit_amount', 0.0),
                    precision_rounding=hour_rounding)
            repartition_employee[employee_id][
                '__domain_' +
                data['timesheet_invoice_type']] = data['__domain']

        # compute total
        for employee_id, vals in repartition_employee.items():
            repartition_employee[employee_id]['total'] = sum(
                [vals[inv_type] for inv_type in billable_types])

        hours_per_employee = [
            repartition_employee[employee_id]['total']
            for employee_id in repartition_employee
        ]
        values['repartition_employee_max'] = max(
            hours_per_employee) if hours_per_employee else 1
        values['repartition_employee'] = repartition_employee

        return values
Beispiel #15
0
 def _compute_qty_remaining(self):
     for wo in self:
         wo.qty_remaining = float_round(
             wo.qty_production - wo.qty_produced,
             precision_rounding=wo.production_id.product_uom_id.rounding)
Beispiel #16
0
    def record_production(self):
        self.ensure_one()
        if self.qty_producing <= 0:
            raise UserError(
                _('Please set the quantity you are currently producing. It should be different from zero.'
                  ))

        if (self.production_id.product_id.tracking !=
                'none') and not self.final_lot_id and self.move_raw_ids:
            raise UserError(
                _('You should provide a lot/serial number for the final product'
                  ))

        # Update quantities done on each raw material line
        # For each untracked component without any 'temporary' move lines,
        # (the new workorder tablet view allows registering consumed quantities for untracked components)
        # we assume that only the theoretical quantity was used
        for move in self.move_raw_ids:
            if move.has_tracking == 'none' and (move.state not in ('done', 'cancel')) and move.bom_line_id\
                        and move.unit_factor and not move.move_line_ids.filtered(lambda ml: not ml.done_wo):
                rounding = move.product_uom.rounding
                if self.product_id.tracking != 'none':
                    qty_to_add = float_round(self.qty_producing *
                                             move.unit_factor,
                                             precision_rounding=rounding)
                    move._generate_consumed_move_line(qty_to_add,
                                                      self.final_lot_id)
                else:
                    move.quantity_done += float_round(
                        self.qty_producing * move.unit_factor,
                        precision_rounding=rounding)

        # Transfer quantities from temporary to final move lots or make them final
        for move_line in self.active_move_line_ids:
            # Check if move_line already exists
            if move_line.qty_done <= 0:  # rounding...
                move_line.sudo().unlink()
                continue
            if move_line.product_id.tracking != 'none' and not move_line.lot_id:
                raise UserError(
                    _('You should provide a lot/serial number for a component')
                )
            # Search other move_line where it could be added:
            lots = self.move_line_ids.filtered(
                lambda x: (x.lot_id.id == move_line.lot_id.id) and
                (not x.lot_produced_id) and (not x.done_move) and
                (x.product_id == move_line.product_id))
            if lots:
                lots[0].qty_done += move_line.qty_done
                lots[0].lot_produced_id = self.final_lot_id.id
                move_line.sudo().unlink()
            else:
                move_line.lot_produced_id = self.final_lot_id.id
                move_line.done_wo = True

        # One a piece is produced, you can launch the next work order
        if self.next_work_order_id.state == 'pending':
            self.next_work_order_id.state = 'ready'

        self.move_line_ids.filtered(
            lambda move_line: not move_line.done_move and not move_line.
            lot_produced_id and move_line.qty_done > 0).write({
                'lot_produced_id':
                self.final_lot_id.id,
                'lot_produced_qty':
                self.qty_producing
            })

        # If last work order, then post lots used
        # TODO: should be same as checking if for every workorder something has been done?
        if not self.next_work_order_id:
            production_moves = self.production_id.move_finished_ids.filtered(
                lambda x: (x.state not in ('done', 'cancel')))
            for production_move in production_moves:
                if production_move.product_id.id == self.production_id.product_id.id and production_move.has_tracking != 'none':
                    move_line = production_move.move_line_ids.filtered(
                        lambda x: x.lot_id.id == self.final_lot_id.id)
                    if move_line:
                        move_line.product_uom_qty += self.qty_producing
                    else:
                        move_line.create({
                            'move_id':
                            production_move.id,
                            'product_id':
                            production_move.product_id.id,
                            'lot_id':
                            self.final_lot_id.id,
                            'product_uom_qty':
                            self.qty_producing,
                            'product_uom_id':
                            production_move.product_uom.id,
                            'qty_done':
                            self.qty_producing,
                            'workorder_id':
                            self.id,
                            'location_id':
                            production_move.location_id.id,
                            'location_dest_id':
                            production_move.location_dest_id.id,
                        })
                elif production_move.unit_factor:
                    rounding = production_move.product_uom.rounding
                    production_move.quantity_done += float_round(
                        self.qty_producing * production_move.unit_factor,
                        precision_rounding=rounding)
                else:
                    production_move.quantity_done += self.qty_producing

        if not self.next_work_order_id:
            for by_product_move in self.production_id.move_finished_ids.filtered(
                    lambda x: (x.product_id.id != self.production_id.product_id
                               .id) and (x.state not in ('done', 'cancel'))):
                if by_product_move.has_tracking == 'none':
                    by_product_move.quantity_done += self.qty_producing * by_product_move.unit_factor

        # Update workorder quantity produced
        self.qty_produced += self.qty_producing

        if self.final_lot_id:
            self.final_lot_id.use_next_on_work_order_id = self.next_work_order_id
            self.final_lot_id = False

        # Set a qty producing
        rounding = self.production_id.product_uom_id.rounding
        if float_compare(self.qty_produced,
                         self.production_id.product_qty,
                         precision_rounding=rounding) >= 0:
            self.qty_producing = 0
        elif self.production_id.product_id.tracking == 'serial':
            self._assign_default_final_lot_id()
            self.qty_producing = 1.0
            self._generate_lot_ids()
        else:
            self.qty_producing = float_round(self.production_id.product_qty -
                                             self.qty_produced,
                                             precision_rounding=rounding)
            self._generate_lot_ids()

        if self.next_work_order_id and self.production_id.product_id.tracking != 'none':
            self.next_work_order_id._assign_default_final_lot_id()

        if float_compare(self.qty_produced,
                         self.production_id.product_qty,
                         precision_rounding=rounding) >= 0:
            self.button_finish()
        return True
Beispiel #17
0
 def _onchange_qty_producing(self):
     """ Update stock.move.lot records, according to the new qty currently
     produced. """
     moves = self.move_raw_ids.filtered(
         lambda move: move.state not in
         ('done', 'cancel') and move.product_id.tracking != 'none' and move.
         product_id.id != self.production_id.product_id.id)
     for move in moves:
         move_lots = self.active_move_line_ids.filtered(
             lambda move_lot: move_lot.move_id == move)
         if not move_lots:
             continue
         rounding = move.product_uom.rounding
         new_qty = float_round(move.unit_factor * self.qty_producing,
                               precision_rounding=rounding)
         if move.product_id.tracking == 'lot':
             move_lots[0].product_qty = new_qty
             move_lots[0].qty_done = new_qty
         elif move.product_id.tracking == 'serial':
             # Create extra pseudo record
             qty_todo = float_round(new_qty -
                                    sum(move_lots.mapped('qty_done')),
                                    precision_rounding=rounding)
             if float_compare(qty_todo, 0.0,
                              precision_rounding=rounding) > 0:
                 while float_compare(
                         qty_todo, 0.0, precision_rounding=rounding) > 0:
                     self.active_move_line_ids += self.env[
                         'stock.move.line'].new({
                             'move_id':
                             move.id,
                             'product_id':
                             move.product_id.id,
                             'lot_id':
                             False,
                             'product_uom_qty':
                             0.0,
                             'product_uom_id':
                             move.product_uom.id,
                             'qty_done':
                             min(1.0, qty_todo),
                             'workorder_id':
                             self.id,
                             'done_wo':
                             False,
                             'location_id':
                             move.location_id.id,
                             'location_dest_id':
                             move.location_dest_id.id,
                         })
                     qty_todo -= 1
             elif float_compare(qty_todo, 0.0,
                                precision_rounding=rounding) < 0:
                 qty_todo = abs(qty_todo)
                 for move_lot in move_lots:
                     if float_compare(
                             qty_todo, 0, precision_rounding=rounding) <= 0:
                         break
                     if not move_lot.lot_id and float_compare(
                             qty_todo,
                             move_lot.qty_done,
                             precision_rounding=rounding) >= 0:
                         qty_todo = float_round(qty_todo -
                                                move_lot.qty_done,
                                                precision_rounding=rounding)
                         self.active_move_line_ids -= move_lot  # Difference operator
                     else:
                         #move_lot.product_qty = move_lot.product_qty - qty_todo
                         if float_compare(move_lot.qty_done - qty_todo,
                                          0,
                                          precision_rounding=rounding) == 1:
                             move_lot.qty_done = move_lot.qty_done - qty_todo
                         else:
                             move_lot.qty_done = 0
                         qty_todo = 0
Beispiel #18
0
    def compute_landed_cost(self):
        AdjustementLines = self.env['stock.valuation.adjustment.lines']
        AdjustementLines.search([('cost_id', 'in', self.ids)]).unlink()

        digits = dp.get_precision('Product Price')(self._cr)
        towrite_dict = {}
        for cost in self.filtered(lambda cost: cost.picking_ids):
            total_qty = 0.0
            total_cost = 0.0
            total_weight = 0.0
            total_volume = 0.0
            total_line = 0.0
            all_val_line_values = cost.get_valuation_lines()
            for val_line_values in all_val_line_values:
                for cost_line in cost.cost_lines:
                    val_line_values.update({
                        'cost_id': cost.id,
                        'cost_line_id': cost_line.id
                    })
                    self.env['stock.valuation.adjustment.lines'].create(
                        val_line_values)
                total_qty += val_line_values.get('quantity', 0.0)
                total_weight += val_line_values.get('weight', 0.0)
                total_volume += val_line_values.get('volume', 0.0)

                former_cost = val_line_values.get('former_cost', 0.0)
                # round this because former_cost on the valuation lines is also rounded
                total_cost += tools.float_round(
                    former_cost,
                    precision_digits=digits[1]) if digits else former_cost

                total_line += 1

            for line in cost.cost_lines:
                value_split = 0.0
                for valuation in cost.valuation_adjustment_lines:
                    value = 0.0
                    if valuation.cost_line_id and valuation.cost_line_id.id == line.id:
                        if line.split_method == 'by_quantity' and total_qty:
                            per_unit = (line.price_unit / total_qty)
                            value = valuation.quantity * per_unit
                        elif line.split_method == 'by_weight' and total_weight:
                            per_unit = (line.price_unit / total_weight)
                            value = valuation.weight * per_unit
                        elif line.split_method == 'by_volume' and total_volume:
                            per_unit = (line.price_unit / total_volume)
                            value = valuation.volume * per_unit
                        elif line.split_method == 'equal':
                            value = (line.price_unit / total_line)
                        elif line.split_method == 'by_current_cost_price' and total_cost:
                            per_unit = (line.price_unit / total_cost)
                            value = valuation.former_cost * per_unit
                        else:
                            value = (line.price_unit / total_line)

                        if digits:
                            value = tools.float_round(
                                value,
                                precision_digits=digits[1],
                                rounding_method='UP')
                            fnc = min if line.price_unit > 0 else max
                            value = fnc(value, line.price_unit - value_split)
                            value_split += value

                        if valuation.id not in towrite_dict:
                            towrite_dict[valuation.id] = value
                        else:
                            towrite_dict[valuation.id] += value
        for key, value in towrite_dict.items():
            AdjustementLines.browse(key).write(
                {'additional_landed_cost': value})
        return True
Beispiel #19
0
    def render(self,
               reference,
               amount,
               currency_id,
               partner_id=False,
               values=None):
        """ Renders the form template of the given acquirer as a qWeb template.
        :param string reference: the transaction reference
        :param float amount: the amount the buyer has to pay
        :param currency_id: currency id
        :param dict partner_id: optional partner_id to fill values
        :param dict values: a dictionary of values for the transction that is
        given to the acquirer-specific method generating the form values

        All templates will receive:

         - acquirer: the payment.acquirer browse record
         - user: the current user browse record
         - currency_id: id of the transaction currency
         - amount: amount of the transaction
         - reference: reference of the transaction
         - partner_*: partner-related values
         - partner: optional partner browse record
         - 'feedback_url': feedback URL, controler that manage answer of the acquirer (without base url) -> FIXME
         - 'return_url': URL for coming back after payment validation (wihout base url) -> FIXME
         - 'cancel_url': URL if the client cancels the payment -> FIXME
         - 'error_url': URL if there is an issue with the payment -> FIXME
         - context: actpy context

        """
        if values is None:
            values = {}

        # reference and amount
        values.setdefault('reference', reference)
        amount = float_round(amount, 2)
        values.setdefault('amount', amount)

        # currency id
        currency_id = values.setdefault('currency_id', currency_id)
        if currency_id:
            currency = self.env['res.currency'].browse(currency_id)
        else:
            currency = self.env.user.company_id.currency_id
        values['currency'] = currency

        # Fill partner_* using values['partner_id'] or partner_id argument
        partner_id = values.get('partner_id', partner_id)
        billing_partner_id = values.get('billing_partner_id', partner_id)
        if partner_id:
            partner = self.env['res.partner'].browse(partner_id)
            if partner_id != billing_partner_id:
                billing_partner = self.env['res.partner'].browse(
                    billing_partner_id)
            else:
                billing_partner = partner
            values.update({
                'partner':
                partner,
                'partner_id':
                partner_id,
                'partner_name':
                partner.name,
                'partner_lang':
                partner.lang,
                'partner_email':
                partner.email,
                'partner_zip':
                partner.zip,
                'partner_city':
                partner.city,
                'partner_address':
                _partner_format_address(partner.street, partner.street2),
                'partner_country_id':
                partner.country_id.id,
                'partner_country':
                partner.country_id,
                'partner_phone':
                partner.phone,
                'partner_state':
                partner.state_id,
                'billing_partner':
                billing_partner,
                'billing_partner_id':
                billing_partner_id,
                'billing_partner_name':
                billing_partner.name,
                'billing_partner_commercial_company_name':
                billing_partner.commercial_company_name,
                'billing_partner_lang':
                billing_partner.lang,
                'billing_partner_email':
                billing_partner.email,
                'billing_partner_zip':
                billing_partner.zip,
                'billing_partner_city':
                billing_partner.city,
                'billing_partner_address':
                _partner_format_address(billing_partner.street,
                                        billing_partner.street2),
                'billing_partner_country_id':
                billing_partner.country_id.id,
                'billing_partner_country':
                billing_partner.country_id,
                'billing_partner_phone':
                billing_partner.phone,
                'billing_partner_state':
                billing_partner.state_id,
            })
        if values.get('partner_name'):
            values.update({
                'partner_first_name':
                _partner_split_name(values.get('partner_name'))[0],
                'partner_last_name':
                _partner_split_name(values.get('partner_name'))[1],
            })
        if values.get('billing_partner_name'):
            values.update({
                'billing_partner_first_name':
                _partner_split_name(values.get('billing_partner_name'))[0],
                'billing_partner_last_name':
                _partner_split_name(values.get('billing_partner_name'))[1],
            })

        # Fix address, country fields
        if not values.get('partner_address'):
            values['address'] = _partner_format_address(
                values.get('partner_street', ''),
                values.get('partner_street2', ''))
        if not values.get('partner_country') and values.get(
                'partner_country_id'):
            values['country'] = self.env['res.country'].browse(
                values.get('partner_country_id'))
        if not values.get('billing_partner_address'):
            values['billing_address'] = _partner_format_address(
                values.get('billing_partner_street', ''),
                values.get('billing_partner_street2', ''))
        if not values.get('billing_partner_country') and values.get(
                'billing_partner_country_id'):
            values['billing_country'] = self.env['res.country'].browse(
                values.get('billing_partner_country_id'))

        # compute fees
        fees_method_name = '%s_compute_fees' % self.provider
        if hasattr(self, fees_method_name):
            fees = getattr(self,
                           fees_method_name)(values['amount'],
                                             values['currency_id'],
                                             values.get('partner_country_id'))
            values['fees'] = float_round(fees, 2)

        # call <name>_form_generate_values to update the tx dict with acqurier specific values
        cust_method_name = '%s_form_generate_values' % (self.provider)
        if hasattr(self, cust_method_name):
            method = getattr(self, cust_method_name)
            values = method(values)

        values.update({
            'tx_url':
            self._context.get('tx_url', self.get_form_action_url()),
            'submit_class':
            self._context.get('submit_class', 'btn btn-link'),
            'submit_txt':
            self._context.get('submit_txt'),
            'acquirer':
            self,
            'user':
            self.env.user,
            'context':
            self._context,
            'type':
            values.get('type') or 'form',
        })
        values.setdefault('return_url', False)

        return self.view_template_id.render(values, engine='ir.qweb')
Beispiel #20
0
    def explode(self, product, quantity, picking_type=False):
        """
            Explodes the BoM and creates two lists with all the information you need: bom_done and line_done
            Quantity describes the number of times you need the BoM: so the quantity divided by the number created by the BoM
            and converted into its UoM
        """
        from collections import defaultdict

        graph = defaultdict(list)
        V = set()

        def check_cycle(v, visited, recStack, graph):
            visited[v] = True
            recStack[v] = True
            for neighbour in graph[v]:
                if visited[neighbour] == False:
                    if check_cycle(neighbour, visited, recStack,
                                   graph) == True:
                        return True
                elif recStack[neighbour] == True:
                    return True
            recStack[v] = False
            return False

        boms_done = [(self, {
            'qty': quantity,
            'product': product,
            'original_qty': quantity,
            'parent_line': False
        })]
        lines_done = []
        V |= set([product.product_tmpl_id.id])

        bom_lines = [(bom_line, product, quantity, False)
                     for bom_line in self.bom_line_ids]
        for bom_line in self.bom_line_ids:
            V |= set([bom_line.product_id.product_tmpl_id.id])
            graph[product.product_tmpl_id.id].append(
                bom_line.product_id.product_tmpl_id.id)
        while bom_lines:
            current_line, current_product, current_qty, parent_line = bom_lines[
                0]
            bom_lines = bom_lines[1:]

            if current_line._skip_bom_line(current_product):
                continue

            line_quantity = current_qty * current_line.product_qty
            bom = self._bom_find(product=current_line.product_id,
                                 picking_type=picking_type
                                 or self.picking_type_id,
                                 company_id=self.company_id.id)
            if bom.type == 'phantom':
                converted_line_quantity = current_line.product_uom_id._compute_quantity(
                    line_quantity / bom.product_qty, bom.product_uom_id)
                bom_lines = [(line, current_line.product_id,
                              converted_line_quantity, current_line)
                             for line in bom.bom_line_ids] + bom_lines
                for bom_line in bom.bom_line_ids:
                    graph[current_line.product_id.product_tmpl_id.id].append(
                        bom_line.product_id.product_tmpl_id.id)
                    if bom_line.product_id.product_tmpl_id.id in V and check_cycle(
                            bom_line.product_id.product_tmpl_id.id,
                        {key: False
                         for key in V}, {key: False
                                         for key in V}, graph):
                        raise UserError(
                            _('Recursion error!  A product with a Bill of Material should not have itself in its BoM or child BoMs!'
                              ))
                    V |= set([bom_line.product_id.product_tmpl_id.id])
                boms_done.append((bom, {
                    'qty': converted_line_quantity,
                    'product': current_product,
                    'original_qty': quantity,
                    'parent_line': current_line
                }))
            else:
                # We round up here because the user expects that if he has to consume a little more, the whole UOM unit
                # should be consumed.
                rounding = current_line.product_uom_id.rounding
                line_quantity = float_round(line_quantity,
                                            precision_rounding=rounding,
                                            rounding_method='UP')
                lines_done.append((current_line, {
                    'qty': line_quantity,
                    'product': current_product,
                    'original_qty': quantity,
                    'parent_line': parent_line
                }))

        return boms_done, lines_done