Пример #1
0
 def do_produce(self):
     # Nothing to do for lots since values are created using default data (stock.move.lots)
     moves = self.production_id.move_raw_ids
     quantity = self.product_qty
     if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0:
         raise UserError(_('You should at least produce some quantity'))
     for move in moves.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel')):
         if move.unit_factor:
             rounding = move.product_uom.rounding
             move.quantity_done_store += float_round(quantity * move.unit_factor, precision_rounding=rounding)
     moves = self.production_id.move_finished_ids.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel'))
     for move in moves:
         rounding = move.product_uom.rounding
         if move.product_id.id == self.production_id.product_id.id:
             move.quantity_done_store += float_round(quantity, precision_rounding=rounding)
         elif move.unit_factor:
             # byproducts handling
             move.quantity_done_store += float_round(quantity * move.unit_factor, precision_rounding=rounding)
     self.check_finished_move_lots()
     if self.production_id.state == 'confirmed':
         self.production_id.write({
             'state': 'progress',
             'date_start': datetime.now(),
         })
     return {'type': 'ir.actions.act_window_close'}
Пример #2
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)
            value_to_return = value if quantity is None or not self.value else self.value
            vals = {
                'price_unit': price_unit,
                'value': value_to_return,
                '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)
                value_to_return = value if quantity is None or not self.value else self.value
                vals.update({
                    'price_unit': self.product_id.standard_price,
                    'value': value_to_return,
                })
            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 = 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)
            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)
                value_to_return = value if quantity is None else self.value + value
                self.write({
                    'value': value_to_return,
                    'price_unit': value / valued_quantity,
                })
        elif self._is_dropshipped() or self._is_dropshipped_returned():
            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)
            value_to_return = value if self._is_dropshipped() else -value
            # In move have a positive value, out move have a negative value, let's arbitrary say
            # dropship are positive.
            self.write({
                'value': value_to_return,
                'price_unit': price_unit if self._is_dropshipped() else -price_unit,
            })
            return value_to_return
Пример #3
0
 def do_produce(self):
     # Nothing to do for lots since values are created using default data (stock.move.lots)
     quantity = self.product_qty
     if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0:
         raise UserError(_('You should at least produce some quantity'))
     for move in self.production_id.move_raw_ids:
         # TODO currently not possible to guess if the user updated quantity by hand or automatically by the produce wizard.
         if move.product_id.tracking == 'none' and move.quantity_done < move.product_uom_qty and move.state not in ('done', 'cancel') and move.unit_factor:
             rounding = move.product_uom.rounding
             move.quantity_done += float_round(quantity * move.unit_factor, precision_rounding=rounding)
     for move in self.production_id.move_finished_ids:
         if move.product_id.tracking == 'none' and move.state not in ('done', 'cancel'):
             rounding = move.product_uom.rounding
             if move.product_id.id == self.production_id.product_id.id:
                 move.quantity_done += float_round(quantity, precision_rounding=rounding)
             elif move.unit_factor:
                 # byproducts handling
                 move.quantity_done += float_round(quantity * move.unit_factor, precision_rounding=rounding)
     self.check_finished_move_lots()
     if self.production_id.state == 'confirmed':
         self.production_id.write({
             'state': 'progress',
             'date_start': datetime.now(),
         })
     return {'type': 'ir.actions.act_window_close'}
    def do_produce(self):
        # apply work time to work orders
        self.update_work_time()

        # if work time was successfully applied to the work orders:
        # complete the work orders
        self.complete_workorders()

        moves = self.production_id.move_raw_ids
        quantity = self.product_qty
        if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0:
            raise UserError(_('You should at least produce some quantity'))
        for move in moves.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel')):
            if move.unit_factor:
                rounding = move.product_uom.rounding
                move.quantity_done_store += float_round(quantity * move.unit_factor, precision_rounding=rounding)
        moves = self.production_id.move_finished_ids.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel'))
        for move in moves:
            rounding = move.product_uom.rounding
            if move.product_id.id == self.production_id.product_id.id:
                move.quantity_done_store += float_round(quantity, precision_rounding=rounding)
            elif move.unit_factor:
                # byproducts handling
                move.quantity_done_store += float_round(quantity * move.unit_factor, precision_rounding=rounding)
        self.check_finished_move_lots()
        if self.production_id.state == 'confirmed':
            self.production_id.write({
                'state': 'progress',
                'date_start': fields.datetime.now(),
            })
        return {'type': 'ir.actions.act_window_close'}
Пример #5
0
    def planned_qty(self, to_date=None):
        """Return planned net move quantity, for select products, through the given date"""

        # build domain for searching mrp.material_plan records
        domain = [('product_id', 'in', self.ids)]
        if to_date:
            domain += [('date_finish', '<', to_date)]
        domain_in = domain + [('move_type', '=', 'supply')]
        domain_out = domain + [('move_type', '=', 'demand')]

        # get moves
        MrpPlan = self.env['mrp.material_plan']
        moves_in = dict((item['product_id'][0], item['product_qty']) for item in
                        MrpPlan.read_group(domain_in, ['product_id', 'product_qty'], ['product_id']))
        moves_out = dict((item['product_id'][0], item['product_qty']) for item in
                         MrpPlan.read_group(domain_out, ['product_id', 'product_qty'], ['product_id']))

        # return dict
        res = dict()
        for product in self.with_context(prefetch_fields=False):
            qty_in = float_round(moves_in.get(product.id, 0.0), precision_rounding=product.uom_id.rounding)
            qty_out = float_round(moves_out.get(product.id, 0.0), precision_rounding=product.uom_id.rounding)
            res[product.id] = {
                'qty_in': qty_in,
                'qty_out': qty_out,
                'qty_net': float_round(qty_in - qty_out, precision_rounding=product.uom_id.rounding),
            }
        return res
    def button_apply_work(self):
        self.ensure_one()

        detail_time = sum(self.detail_ids.mapped('minutes_assigned')) / 60 or 0.0
        rounded_detail_time = float_round(detail_time, precision_digits=2)
        rounded_total_time = float_round(self.total_hours, precision_digits=2)
        if rounded_detail_time != rounded_total_time:
            raise UserError(
                "Time assigned on detail lines ({} hours) doesn't sum to the "
                "total time on the batch ({} hours)".format(
                    rounded_detail_time, rounded_total_time))

        # check for canceled or completed orders
        comp_lines = self.detail_ids.filtered(
            lambda x: x.production_state in ('done', 'cancel'))
        if comp_lines:
            message = "The following orders have already been completed (or " \
                      "canceled).  You should reassign orders.\n"
            message += ", ".join(comp_lines.mapped('production_id.name'))
            raise UserError(message)

        for detail in self.detail_ids.filtered(lambda r: r.production_id):
            mo = detail.production_id
            _logger.info(mo.name)
            if not mo.product_id.id == detail.product_id.id:
                raise UserError(
                    "{} does not match the product on {} ({})".format(
                        detail.product_id.default_code,
                        mo.name,
                        mo.product_id.default_code))
            if mo.state == 'confirmed':
                mo.button_plan()
            if detail.actual_quantity != mo.product_qty:
                change_wiz = self.env['change.production.qty'].create(
                    {'mo_id': mo.id, 'product_qty': detail.actual_quantity})
                change_wiz.change_prod_qty()
            ctx = dict(self.env.context)
            ctx['default_production_id'] = mo.id
            produce_wiz = self.env['mrp.wo.produce'].with_context(ctx).create({'production_id': mo.id})

            # divide time evenly across workorders
            produce_wiz.load_work()
            wo_count = len(produce_wiz.work_ids)
            for work in produce_wiz.work_ids:
                if detail.minutes_assigned <= 1.0:
                    raise UserError("Work time cannot be less than 1 minute per manufacturing order")
                labor_time = (detail.minutes_assigned/60)/wo_count
                work.update({
                    'user_id': self.work_user_id.id,
                    'labor_date': self.work_date,
                    'labor_time': labor_time,
                })
            produce_wiz.do_produce()
            mo.button_mark_done()

        self.state = 'closed'
Пример #7
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)
Пример #8
0
    def adyen_form_generate_values(self, values):
        base_url = self.env["ir.config_parameter"].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": "%s" % urlparse.urljoin(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": "%s" % urlparse.urljoin(base_url, AdyenController._return_url),
                    "merchantReturnData": json.dumps({"return_url": "%s" % values.pop("return_url")})
                    if values.get("return_url")
                    else False,
                    "merchantSig": self._adyen_generate_merchant_sig("in", values),
                }
            )

        return values
Пример #9
0
 def _get_operation_line(self, routing, qty, level):
     operations = []
     total = 0.0
     for operation in routing.operation_ids:
         operation_cycle = float_round(qty / operation.workcenter_id.capacity, precision_rounding=1, rounding_method='UP')
         duration_expected = operation_cycle * operation.time_cycle + operation.workcenter_id.time_stop + operation.workcenter_id.time_start
         total = ((duration_expected / 60.0) * operation.workcenter_id.costs_hour)
         operations.append({
             'level': level or 0,
             'operation': operation,
             'name': operation.name + ' - ' + operation.workcenter_id.name,
             'duration_expected': duration_expected,
             'total': float_round(total, precision_rounding=self.env.user.company_id.currency_id.rounding),
         })
     return operations
Пример #10
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)})
        return tx
Пример #11
0
 def _quant_create_from_move(self, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location_from=False, force_location_to=False):
     quant = super(StockQuant, self)._quant_create_from_move(qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location_from=force_location_from, force_location_to=force_location_to)
     quant._account_entry_move(move)
     if move.product_id.valuation == 'real_time':
         # If the precision required for the variable quant cost is larger than the accounting
         # precision, inconsistencies between the stock valuation and the accounting entries
         # may arise.
         # For example, a box of 13 units is bought 15.00. If the products leave the
         # stock one unit at a time, the amount related to the cost will correspond to
         # round(15/13, 2)*13 = 14.95. To avoid this case, we split the quant in 12 + 1, then
         # record the difference on the new quant.
         # We need to make sure to able to extract at least one unit of the product. There is
         # an arbitrary minimum quantity set to 2.0 from which we consider we can extract a
         # unit and adapt the cost.
         curr_rounding = move.company_id.currency_id.rounding
         cost_rounded = float_round(quant.cost, precision_rounding=curr_rounding)
         cost_correct = cost_rounded
         if float_compare(quant.product_id.uom_id.rounding, 1.0, precision_digits=1) == 0\
                 and float_compare(quant.qty * quant.cost, quant.qty * cost_rounded, precision_rounding=curr_rounding) != 0\
                 and float_compare(quant.qty, 2.0, precision_rounding=quant.product_id.uom_id.rounding) >= 0:
             quant_correct = quant._quant_split(quant.qty - 1.0)
             cost_correct += (quant.qty * quant.cost) - (quant.qty * cost_rounded)
             quant.sudo().write({'cost': cost_rounded})
             quant_correct.sudo().write({'cost': cost_correct})
     return quant
Пример #12
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)})
        return tx
Пример #13
0
 def _update_finished_move(self):
     """ Update the finished move & move lines in order to set the finished
     product lot on it as well as the produced quantity. This method get the
     information either from the last workorder or from the Produce wizard."""
     production_move = self.production_id.move_finished_ids.filtered(
         lambda move: move.product_id == self.product_id and
         move.state not in ('done', 'cancel')
     )
     if production_move and production_move.product_id.tracking != 'none':
         if not self.final_lot_id:
             raise UserError(_('You need to provide a lot for the finished product.'))
         move_line = production_move.move_line_ids.filtered(
             lambda line: line.lot_id.id == self.final_lot_id.id
         )
         if move_line:
             if self.product_id.tracking == 'serial':
                 raise UserError(_('You cannot produce the same serial number twice.'))
             move_line.product_uom_qty += self.qty_producing
             move_line.qty_done += 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': self.product_uom_id.id,
                 'qty_done': self.qty_producing,
                 'location_id': production_move.location_id.id,
                 'location_dest_id': production_move.location_dest_id.id,
             })
     else:
         rounding = production_move.product_uom.rounding
         production_move._set_quantity_done(
             production_move.quantity_done + float_round(self.qty_producing, precision_rounding=rounding)
         )
Пример #14
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
Пример #15
0
    def _get_bom_lines(self, bom, bom_quantity, product, line_id, level):
        components = []
        total = 0
        for line in bom.bom_line_ids:
            line_quantity = (bom_quantity / (bom.product_qty or 1.0)) * line.product_qty
            if line._skip_bom_line(product):
                continue
            price = line.product_id.uom_id._compute_price(line.product_id.standard_price, line.product_uom_id) * line_quantity
            if line.child_bom_id:
                factor = float_round(line.product_uom_id._compute_quantity(line_quantity, line.child_bom_id.product_uom_id) / line.child_bom_id.product_qty, precision_rounding=1, rounding_method='UP')
                sub_total = self._get_price(line.child_bom_id, factor)
            else:
                sub_total = price
            components.append({
                'prod_id': line.product_id.id,
                'prod_name': line.product_id.display_name,
                'code': line.child_bom_id and self._get_bom_reference(line.child_bom_id) or '',
                'prod_qty': line_quantity,
                'prod_uom': line.product_uom_id.name,
                'prod_cost': price,
                'parent_id': bom.id,
                'line_id': line.id,
                'level': level or 0,
                'total': sub_total,
                'child_bom': line.child_bom_id.id,
                'phantom_bom': line.child_bom_id and line.child_bom_id.type == 'phantom' or False,
                'attachments': self.env['mrp.document'].search(['|', '&',
                    ('res_model', '=', 'product.product'), ('res_id', '=', line.product_id.id), '&', ('res_model', '=', 'product.template'), ('res_id', '=', line.product_id.product_tmpl_id.id)]),

            })
            total += sub_total
        return components, total
Пример #16
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
        """
        boms_done = [(self, {'qty': quantity, 'product': product, 'original_qty': quantity, 'parent_line': False})]
        lines_done = []
        templates_done = product.product_tmpl_id

        bom_lines = [(bom_line, product, quantity, False) for bom_line in self.bom_line_ids]
        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
            if current_line.product_id.product_tmpl_id in templates_done:
                raise UserError(_('Recursion error!  A product with a Bill of Material should not have itself in its BoM or child BoMs!'))

            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
                templates_done |= current_line.product_id.product_tmpl_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
Пример #17
0
 def _run_valuation(self, quantity=None):
     self.ensure_one()
     if self._is_in():
         if self.product_id.cost_method in ['fifo', 'average']:
             price_unit = self._get_price_unit()
             value = price_unit * (quantity or self.product_qty)
             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,
             }
             if self.product_id.cost_method == 'fifo':
                 vals['remaining_qty'] = self.product_qty if quantity is None else self.remaining_qty + quantity
             self.write(vals)
         else:  # standard
             value = self.product_id.standard_price * (quantity or self.product_qty)
             self.write({
                 'price_unit': self.product_id.standard_price,
                 'value': value if quantity is None or not self.value else self.value,
             })
     elif self._is_out():
         if self.product_id.cost_method == 'fifo':
             self.env['stock.move']._run_fifo(self, quantity=quantity)
         elif self.product_id.cost_method in ['standard', 'average']:
             curr_rounding = self.company_id.currency_id.rounding
             value = -float_round(self.product_id.standard_price * (self.product_qty 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 / self.product_qty,
             })
Пример #18
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()
Пример #19
0
 def _action_done(self):
     self.product_price_update_before_done()
     res = super(StockMove, self)._action_done()
     for move in res:
         if move._is_in():
             if move.product_id.cost_method in ['fifo', 'average']:
                 price_unit = move.price_unit or move._get_price_unit()
                 value = price_unit * move.product_qty
                 vals = {
                     'price_unit': price_unit,
                     'value': value,
                     'remaining_value': value,
                 }
                 if move.product_id.cost_method == 'fifo':
                     vals['remaining_qty'] = move.product_qty
                 move.write(vals)
             else:  # standard
                 move.write({
                     'price_unit': move.product_id.standard_price,
                     'value': move.product_id.standard_price * move.product_qty,
                 })
         elif move._is_out():
             if move.product_id.cost_method == 'fifo':
                 self.env['stock.move']._run_fifo(move)
             elif move.product_id.cost_method in ['standard', 'average']:
                 curr_rounding = move.company_id.currency_id.rounding
                 value = -float_round(move.product_id.standard_price * move.product_qty, precision_rounding=curr_rounding)
                 move.write({
                     'value': value,
                     'price_unit': value / move.product_qty,
                 })
     for move in res.filtered(lambda m: m.product_id.valuation == 'real_time' and (m._is_in() or m._is_out())):
         move._account_entry_move()
     return res
Пример #20
0
 def ogone_form_generate_values(self, values):
     base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
     ogone_tx_values = dict(values)
     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': 'return_url=%s' % ogone_tx_values.pop('return_url') if ogone_tx_values.get('return_url') else False,
     }
     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
Пример #21
0
    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)
Пример #22
0
    def _get_price(self, bom, factor, product):
        price = 0
        if bom.routing_id:
            # routing are defined on a BoM and don't have a concept of quantity.
            # It means that the operation time are defined for the quantity on
            # the BoM (the user produces a batch of products). E.g the user
            # product a batch of 10 units with a 5 minutes operation, the time
            # will be the 5 for a quantity between 1-10, then doubled for
            # 11-20,...
            operation_cycle = float_round(factor, precision_rounding=1, rounding_method='UP')
            operations = self._get_operation_line(bom.routing_id, operation_cycle, 0)
            price += sum([op['total'] for op in operations])

        for line in bom.bom_line_ids:
            if line._skip_bom_line(product):
                continue
            if line.child_bom_id:
                qty = line.product_uom_id._compute_quantity(line.product_qty * factor, line.child_bom_id.product_uom_id)
                sub_price = self._get_price(line.child_bom_id, qty, line.product_id)
                price += sub_price
            else:
                prod_qty = line.product_qty * factor
                not_rounded_price = line.product_id.uom_id._compute_price(line.product_id.standard_price, line.product_uom_id) * prod_qty
                price += self.env.user.company_id.currency_id.round(not_rounded_price)
        return price
Пример #23
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,
                         'date': move.date,
                     })
                     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
Пример #24
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_cost += val_line_values.get('former_cost', 0.0)
                total_weight += val_line_values.get('weight', 0.0)
                total_volume += val_line_values.get('volume', 0.0)
                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
        if towrite_dict:
            for key, value in towrite_dict.items():
                AdjustementLines.browse(key).write({'additional_landed_cost': value})
        return True
Пример #25
0
    def round(self, amount):
        """Return ``amount`` rounded  according to ``self``'s rounding rules.

           :param float amount: the amount to round
           :return: rounded float
        """
        self.ensure_one()
        return tools.float_round(amount, precision_rounding=self.rounding)
Пример #26
0
 def _get_check_amount_in_words(self, amount):
     # TODO: merge, refactor and complete the amount_to_text and amount_to_text_en classes
     check_amount_in_words = amount_to_text_en.amount_to_text(math.floor(amount), lang='en', currency='')
     check_amount_in_words = check_amount_in_words.replace(' and Zero Cent', '') # Ugh
     decimals = amount % 1
     if decimals >= 10**-2:
         check_amount_in_words += _(' and %s/100') % str(int(round(float_round(decimals*100, precision_rounding=1))))
     return check_amount_in_words
def AmountToTextFractional(amountInt):
    amount_word = amount_to_text_en.amount_to_text(
        math.floor(amountInt), lang='en', currency='')
    amount_word = amount_word.replace(' and Zero Cent', '')
    decimals = amountInt % 1
    if decimals >= 10**-2:
        amount_word += _(' and %s/100') % str(int(round(
            float_round(decimals*100, precision_rounding=1))))
    return amount_word
Пример #28
0
 def _onchange_amount(self):
     if hasattr(super(AccountPayment, self), '_onchange_amount'):
         super(AccountPayment, self)._onchange_amount()
     check_amount_in_words = amount_to_text_en.amount_to_text(math.floor(self.amount), lang='en', currency='')
     check_amount_in_words = check_amount_in_words.replace(' and Zero Cent', '') # Ugh
     decimals = self.amount % 1
     if decimals >= 10**-2:
         check_amount_in_words += _(' and %s/100') % str(int(round(float_round(decimals*100, precision_rounding=1))))
     self.check_amount_in_words = check_amount_in_words
Пример #29
0
 def get_operations(self, bom_id=False, qty=0, level=0):
     bom = self.env['mrp.bom'].browse(bom_id)
     lines = self._get_operation_line(bom.routing_id, float_round(qty / bom.product_qty, precision_rounding=1, rounding_method='UP'), level)
     values = {
         'bom_id': bom_id,
         'currency': self.env.user.company_id.currency_id,
         'operations': lines,
     }
     return self.env.ref('mrp.report_mrp_operation_line').render({'data': values})
Пример #30
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
Пример #31
0
def convert_to_month(value):
    return float_round(value / 12.0,
                       precision_rounding=0.01,
                       rounding_method='DOWN')
Пример #32
0
 def _get_rounded_amount(self, amount):
     if self.config_id.cash_rounding:
         amount = float_round(amount, precision_rounding=self.config_id.rounding_method.rounding, rounding_method=self.config_id.rounding_method.rounding_method)
     currency = self.currency_id
     return currency.round(amount) if currency else amount
Пример #33
0
    def compute_landed_cost(self):
        AdjustementLines = self.env['stock.valuation.adjustment.lines']
        AdjustementLines.search([('cost_id', 'in', self.ids)]).unlink()

        digits = self.env['decimal.precision'].precision_get('Product Price')
        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) 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,
                                                      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
Пример #34
0
 def get_wallet_balance(self, user, include_config=True):
     result = float_round(sum(move['amount'] for move in self.env['lunch.cashmove.report'].search_read(
         [('user_id', '=', user.id)], ['amount'])), precision_digits=2)
     if include_config:
         result += user.company_id.lunch_minimum_threshold
     return result
Пример #35
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 = 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)
            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,
            })
Пример #36
0
            'res_id': self.account_move.id,
        }

    def _is_pos_order_paid(self):
        return float_is_zero(self.amount_total - self.amount_paid, precision_rounding=self.currency_id.rounding)

    def action_pos_order_paid(self):
<<<<<<< HEAD
        if not self._is_pos_order_paid():
=======
        self.ensure_one()

        if not self.config_id.cash_rounding:
            total = self.amount_total
        else:
            total = float_round(self.amount_total, precision_rounding=self.config_id.rounding_method.rounding, rounding_method=self.config_id.rounding_method.rounding_method)

        if not float_is_zero(total - self.amount_paid, precision_rounding=self.currency_id.rounding):
>>>>>>> f0a66d05e70e432d35dc68c9fb1e1cc6e51b40b8
            raise UserError(_("Order %s is not fully paid.") % self.name)

        self.write({'state': 'paid'})

        return True

    def _get_amount_receivable(self):
        return self.amount_total


    def _prepare_invoice_vals(self):
        self.ensure_one()
Пример #37
0
    def test_08_cash_basis_multicurrency_payment_after_invoice(self):
        """Test to validate tax effectively Payable
        My company currency is MXN.

        Invoice issued two days ago in USD at a rate => 1MXN = 0.80 USD.
        Booked like:

            Expenses            1250                1000    USD
            Unpaid Taxes         200                 160    USD
                Payable                 1450       -1160    USD

        Payment issued today in USD at a rate => 1 MXN = 1.25 USD.
        Booked like:

            Payable              928                1160    USD
                Bank                     928       -1160    USD

        This Generates a Exchange Rate Difference.
        Booked like:

            Payable              522                   0    USD
                Gain Exchange rate       522           0    USD

        And a Tax Cash Basis Entry is generated.
        Booked like:

            Tax Base Account     800                1000    USD
                Tax Base Account         800       -1000    USD
            Creditable Tax       128                 160    USD
                Unpaid Taxes             128        -160    USD

        What I expect from here:
            - Base to report to DIOT: Tax Base Account MXN 800.00
            - Creditable Tax MXN 128.00
            - Have a difference of MXN 72.00 for Unpaid Taxes that I would
            later have to issue as a Loss in Exchange Rate Difference

            Loss Exchange rate    72                   0    USD
                Unpaid Taxes              72           0    USD
        """

        invoice_date = self.two_days_ago
        self.company.partner_id.write({
            'property_account_position_id': self.fiscal_position.id,
        })
        invoice_id = self.create_invoice(
            1000,
            invoice_date,
            inv_type='in_invoice',
            currency_id=self.usd.id)

        self.create_payment(
            invoice_id, self.today, 1160, self.bank_journal_usd, self.usd)  # noqa

        base_amls = self.account_move_line_model.search(
            [('account_id', '=', self.account_tax_cash_basis.id)])
        base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance'))
        self.assertEquals(
            float_round(base_at_payment, precision_digits=self.precision),
            800)

        tax_amls = self.account_move_line_model.search(
            [('account_id', '=', self.tax_account.id)])
        tax_diff = sum(tax_amls.mapped('balance'))
        self.assertEquals(
            float_round(tax_diff, precision_digits=self.precision), 72)
Пример #38
0
    def record_production(self):
        self.ensure_one()
        if self.qty_producing < 0:
            raise UserError(
                _('Please set the quantity you produced in the Current Qty field. It can not be 0!'
                  ))

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

        # Update quantities done on each raw material line
        raw_moves = self.move_raw_ids.filtered(
            lambda x: (x.has_tracking == 'none') and
            (x.state not in ('done', 'cancel')) and x.bom_line_id)
        for move in raw_moves:
            if move.unit_factor:
                rounding = move.product_uom.rounding
                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_lot in self.active_move_lot_ids:
            # Check if move_lot already exists
            if move_lot.quantity_done <= 0:  # rounding...
                move_lot.sudo().unlink()
                continue
            if not move_lot.lot_id:
                raise UserError(_('You should provide a lot for a component'))
            # Search other move_lot where it could be added:
            lots = self.move_lot_ids.filtered(
                lambda x: (x.lot_id.id == move_lot.lot_id.id) and
                (not x.lot_produced_id) and (not x.done_move))
            if lots:
                lots[0].quantity_done += move_lot.quantity_done
                lots[0].lot_produced_id = self.final_lot_id.id
                move_lot.sudo().unlink()
            else:
                move_lot.lot_produced_id = self.final_lot_id.id
                move_lot.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'
        if self.next_work_order_id and self.final_lot_id and not self.next_work_order_id.final_lot_id:
            self.next_work_order_id.final_lot_id = self.final_lot_id.id

        self.move_lot_ids.filtered(
            lambda move_lot: not move_lot.done_move and not move_lot.
            lot_produced_id and move_lot.quantity_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.product_id.tracking != 'none':
                    move_lot = production_move.move_lot_ids.filtered(
                        lambda x: x.lot_id.id == self.final_lot_id.id)
                    if move_lot:
                        move_lot.quantity += self.qty_producing
                        move_lot.quantity_done += self.qty_producing
                    else:
                        move_lot.create({
                            'move_id': production_move.id,
                            'lot_id': self.final_lot_id.id,
                            'quantity': self.qty_producing,
                            'quantity_done': self.qty_producing,
                            'workorder_id': self.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  # TODO: UoM conversion?
        # Update workorder quantity produced
        self.qty_produced += self.qty_producing

        # Set a qty producing
        if self.qty_produced >= self.production_id.product_qty:
            self.qty_producing = 0
        elif self.production_id.product_id.tracking == 'serial':
            self.qty_producing = 1.0
            self._generate_lot_ids()
        else:
            self.qty_producing = self.production_id.product_qty - self.qty_produced
            self._generate_lot_ids()

        if self.qty_produced >= self.production_id.product_qty:
            self.button_finish()
        return True
Пример #39
0
    def _onchange_product_qty(self):
        lines = []
        qty_todo = self.product_uom_id._compute_quantity(
            self.product_qty, self.production_id.product_uom_id, round=False)
        for move in self.production_id.move_raw_ids.filtered(
                lambda m: m.state not in ('done', 'cancel') and m.bom_line_id):
            qty_to_consume = float_round(
                qty_todo * move.unit_factor,
                precision_rounding=move.product_uom.rounding)
            for move_line in move.move_line_ids:
                if float_compare(
                        qty_to_consume,
                        0.0,
                        precision_rounding=move.product_uom.rounding) <= 0:
                    break
                if move_line.lot_produced_id or float_compare(
                        move_line.product_uom_qty,
                        move_line.qty_done,
                        precision_rounding=move.product_uom.rounding) <= 0:
                    continue
                to_consume_in_line = min(qty_to_consume,
                                         move_line.product_uom_qty)
                lines.append({
                    'move_id':
                    move.id,
                    'qty_to_consume':
                    to_consume_in_line,
                    'qty_done':
                    to_consume_in_line,
                    'lot_id':
                    move_line.lot_id.id,
                    'product_uom_id':
                    move.product_uom.id,
                    'product_id':
                    move.product_id.id,
                    'qty_reserved':
                    min(to_consume_in_line, move_line.product_uom_qty),
                })
                qty_to_consume -= to_consume_in_line
            if float_compare(qty_to_consume,
                             0.0,
                             precision_rounding=move.product_uom.rounding) > 0:
                if move.product_id.tracking == 'serial':
                    while float_compare(
                            qty_to_consume,
                            0.0,
                            precision_rounding=move.product_uom.rounding) > 0:
                        lines.append({
                            'move_id': move.id,
                            'qty_to_consume': 1,
                            'qty_done': 1,
                            'product_uom_id': move.product_uom.id,
                            'product_id': move.product_id.id,
                        })
                        qty_to_consume -= 1
                else:
                    lines.append({
                        'move_id': move.id,
                        'qty_to_consume': qty_to_consume,
                        'qty_done': qty_to_consume,
                        'product_uom_id': move.product_uom.id,
                        'product_id': move.product_id.id,
                    })

        self.produce_line_ids = [(0, 0, x) for x in lines]
Пример #40
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)
Пример #41
0
    def record_production(self):
        if not self:
            return True

        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)
                elif len(move._get_move_lines()) < 2:
                    move.quantity_done += float_round(
                        self.qty_producing * move.unit_factor,
                        precision_rounding=rounding)
                else:
                    move._set_quantity_done(
                        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
                self._link_to_quality_check(move_line, lots[0])
                move_line.sudo().unlink()
            else:
                move_line.lot_produced_id = self.final_lot_id.id
                move_line.done_wo = True

        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_move = 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 production_move.product_id.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
                    move_line.qty_done += self.qty_producing
                else:
                    location_dest_id = production_move.location_dest_id.get_putaway_strategy(
                        self.product_id
                    ).id or production_move.location_dest_id.id
                    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':
                        location_dest_id,
                    })
            else:
                production_move._set_quantity_done(self.qty_producing)

        if not self.next_work_order_id:
            for by_product_move in self._get_byproduct_move_to_update():
                if by_product_move.has_tracking != 'serial':
                    values = self._get_byproduct_move_line(
                        by_product_move,
                        self.qty_producing * by_product_move.unit_factor)
                    self.env['stock.move.line'].create(values)
                elif by_product_move.has_tracking == 'serial':
                    qty_todo = by_product_move.product_uom._compute_quantity(
                        self.qty_producing * by_product_move.unit_factor,
                        by_product_move.product_id.uom_id)
                    for i in range(
                            0, int(float_round(qty_todo, precision_digits=0))):
                        values = self._get_byproduct_move_line(
                            by_product_move, 1)
                        self.env['stock.move.line'].create(values)

        # 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

        # One a piece is produced, you can launch the next work order
        self._start_nextworkorder()

        # 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.next_work_order_id.state not in [
                'done', 'cancel'
        ] 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
Пример #42
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,
                             'date':
                             move.date,
                         })
                     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
Пример #43
0
    def test_11_invoice_not_company_currency_payment_in_company_currency(self):
        """Test to validate tax effectively Payable

        My company currency is MXN.

        Invoice issued yesterday in USD at a rate => 1MXN = 1 USD.
        Booked like:

            Expenses            1000                1000    USD
            Unpaid Taxes         160                 160    USD
                Payable                 1160       -1160    USD

        Payment issued today in MXN at a rate => 1 MXN = 1.25 USD.
        Booked like:

            Payable              928                   -      -
                Bank                     928           -      -

        This Generates a Exchange Rate Difference.
        Booked like:

            Payable              232                 232    USD
                Gain Exchange rate       522        -232    USD

        And a Tax Cash Basis Entry is generated.
        Booked like:

            Tax Base Account     800                   0    USD
                Tax Base Account         800           0    USD
            Creditable Tax       128                   0    USD  # (I'd expect the same value as in the invoice for amount_currency in tax: 160 USD)  # noqa
                Unpaid Taxes             128           0    USD

        What I expect from here:
            - Base to report to DIOT: Tax Base Account MXN 800.00
            - Creditable Tax MXN 128.00
            - Have a difference of MXN 32.00 for Unpaid Taxes that I would
            later have to issue as a Loss in Exchange Rate Difference

            Loss Exchange rate    32                   0    USD
                Unpaid Taxes              32           0    USD
        """

        invoice_date = self.yesterday
        self.company.partner_id.write({
            'property_account_position_id': self.fiscal_position.id,
        })
        invoice_id = self.create_invoice(
            1000,
            invoice_date,
            inv_type='in_invoice',
            currency_id=self.usd.id)

        self.create_payment(
            invoice_id, self.today, 928, self.bank_journal_mxn, self.mxn)  # noqa

        # Testing that I am fetching the right Tax Base
        base_amls = self.account_move_line_model.search(
            [('account_id', '=', self.account_tax_cash_basis.id)])
        base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance'))
        self.assertEquals(
            float_round(base_at_payment, precision_digits=self.precision),
            800)

        # Testing that I am fetching the right difference in Exchange rate
        tax_amls = self.account_move_line_model.search(
            [('account_id', '=', self.tax_account.id)])
        tax_diff = sum(tax_amls.mapped('balance'))
        self.assertEquals(
            float_round(tax_diff, precision_digits=self.precision), 32)
Пример #44
0
    def test_05_invoice_not_company_currency_payment_in_company_currency(self):
        """Test to validate tax effectively receivable

        My company currency is MXN.

        Invoice issued yesterday in USD at a rate => 1MXN = 1 USD.
        Booked like:

            Receivable          1160                1160    USD
                Revenue                 1000       -1000    USD
                Taxes to Collect         160        -160    USD

        Payment issued today in MXN at a rate => 1 MXN = 1.25 USD.
        Booked like:

            Bank                 928                   -      -
                Receivable               928           -      -

        This Generates a Exchange Rate Difference.
        Booked like:

            Loss Exchange rate   232                 232    USD
                Receivable               232        -232    USD

        And a Tax Cash Basis Entry is generated.
        Booked like:

            Tax Base Account     800                   0    USD
                Tax Base Account         800           0    USD
            Taxes to Collect     128                   0    USD  # (I'd expect the same value as in the invoice for amount_currency in tax: 160 USD)  # noqa
                Taxes to Paid            128           0    USD

        What I expect from here:
            - Base to report to DIOT if it would be the case (not in this
            case): * Tax Base Account MXN 800.00
            - Paid to SAT MXN 128.00
            - Have a difference of MXN -32.00 for Taxes to Collect that I would
            later have to issue as a Gain in Exchange Rate Difference

            Taxes to Collect      32                   0    USD
                Gain Exchange rate        32           0    USD
        """

        invoice_date = self.yesterday
        self.company.partner_id.write({
            'property_account_position_id': self.fiscal_position.id,
        })
        invoice_id = self.create_invoice(
            1000,
            invoice_date,
            currency_id=self.usd.id)

        self.assertEqual(invoice_id.state, "open")
        self.assertEqual(
            invoice_id.invoice_line_ids.invoice_line_tax_ids.
            l10n_mx_cfdi_tax_type, "Tasa")
        self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed",
                         invoice_id.message_ids.mapped('body'))

        xml = invoice_id.l10n_mx_edi_get_xml_etree()
        self.assertEqual(invoice_id.amount_total, float(xml.get('Total')),
                         "Total amount is not right")
        self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed",
                         invoice_id.message_ids.mapped('body'))

        self.create_payment(
            invoice_id, self.today, 928, self.bank_journal_mxn, self.mxn)  # noqa

        # Testing that I am fetching the right Tax Base
        base_amls = self.account_move_line_model.search(
            [('account_id', '=', self.account_tax_cash_basis.id)])
        base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance'))
        self.assertEquals(
            float_round(base_at_payment, precision_digits=self.precision),
            -800)

        # Testing that I am fetching the right difference in Exchange rate
        tax_amls = self.account_move_line_model.search(
            [('account_id', '=', self.tax_account.id)])
        tax_diff = sum(tax_amls.mapped('balance'))
        self.assertEquals(
            float_round(tax_diff, precision_digits=self.precision), -32)
Пример #45
0
 def _get_supplierinfo_pricelist_price(self,
                                       rule,
                                       date=None,
                                       quantity=None,
                                       product_id=None):
     """Method for getting the price from supplier info."""
     self.ensure_one()
     if product_id:
         domain = [
             '|',
             ('product_id', '=', product_id),
             ('product_tmpl_id', '=', self.id),
         ]
     else:
         domain = [
             ('product_tmpl_id', '=', self.id),
         ]
     if not rule.no_supplierinfo_min_quantity and quantity:
         domain += [
             '|',
             ('min_qty', '=', False),
             ('min_qty', '<=', quantity),
         ]
     if date:
         domain += [
             '|',
             ('date_start', '=', False),
             ('date_start', '<=', date),
             '|',
             ('date_end', '=', False),
             ('date_end', '>=', date),
         ]
     # We use a different default order because we are interested in getting
     # the price for lowest minimum quantity if no_supplierinfo_min_quantity
     supplierinfos = self.env['product.supplierinfo'].search(
         domain,
         order='min_qty,sequence,price',
     )
     if rule.no_supplierinfo_min_quantity:
         price = supplierinfos[:1].price
     else:
         price = supplierinfos[-1:].price
     if price:
         # We have to replicate this logic in this method as pricelist
         # method are atomic and we can't hack inside.
         # Verbatim copy of part of product.pricelist._compute_price_rule.
         qty_uom_id = self._context.get('uom') or self.uom_id.id
         price_uom = self.env['product.uom'].browse([qty_uom_id])
         convert_to_price_uom = (
             lambda price: self.uom_id._compute_price(price, price_uom))
         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)
     return price
Пример #46
0
    def test_01_cash_basis_multicurrency_payment_before_invoice(self):
        """Test to validate tax effectively receivable
        My company currency is MXN.

        Invoice issued yesterday in USD at a rate => 1MXN = 1 USD.
        Booked like:

            Receivable          1160                1160    USD
                Revenue                 1000       -1000    USD
                Taxes to Collect         160        -160    USD

        Payment issued two days ago in USD at a rate => 1MXN = 0.80 USD.
        Booked like:

            Bank                1450                1160    USD
                Receivable              1450       -1160    USD

        This Generates a Exchange Rate Difference.
        Booked like:

            Receivable           290                   0    USD
                Gain Exchange rate       290           0    USD

        And a Tax Cash Basis Entry is generated.
        Booked like:

            Tax Base Account    1250                1000    USD
                Tax Base Account        1250       -1000    USD
            Taxes to Collect     200                 160    USD
                Taxes to Paid            200        -160    USD

        What I expect from here:
            - Base to report to DIOT if it would be the case (not in this
            case): * Tax Base Account MXN 1250.00
            - Paid to SAT MXN 200.00
            - Have a difference of MXN 40.00 for Taxes to Collect that I would
            later have to issue as a Loss in Exchange Rate Difference

            Loss Exchange rate    40                   0    USD
                Taxes to Collect          40           0    USD
        """

        invoice_date = self.yesterday
        self.company.partner_id.write({
            'property_account_position_id': self.fiscal_position.id,
        })
        invoice_id = self.create_invoice(
            1000,
            invoice_date,
            currency_id=self.usd.id)

        self.assertEqual(invoice_id.state, "open")
        self.assertEqual(
            invoice_id.invoice_line_ids.invoice_line_tax_ids.
            l10n_mx_cfdi_tax_type, "Tasa")
        self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed",
                         invoice_id.message_ids.mapped('body'))

        xml = invoice_id.l10n_mx_edi_get_xml_etree()
        self.assertEqual(invoice_id.amount_total, float(xml.get('Total')),
                         "Total amount is not right")
        self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed",
                         invoice_id.message_ids.mapped('body'))

        self.create_payment(
            invoice_id, self.two_days_ago, 1160, self.bank_journal_usd, self.usd)  # noqa

        base_amls = self.account_move_line_model.search(
            [('account_id', '=', self.account_tax_cash_basis.id)])
        base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance'))
        self.assertEquals(
            float_round(base_at_payment, precision_digits=self.precision),
            -1250)

        tax_amls = self.account_move_line_model.search(
            [('account_id', '=', self.tax_account.id)])
        tax_diff = sum(tax_amls.mapped('balance'))
        self.assertEquals(
            float_round(tax_diff, precision_digits=self.precision), 40)
Пример #47
0
    def test_06_invoice_company_currency_payment_not_company_currency(self):
        """Test to validate tax effectively receivable

        My company currency is MXN.

        Invoice issued yesterday in MXN at a rate => 1MXN = 1 USD.
        Booked like:

            Receivable          1160                   -      -
                Revenue                 1000           -      -
                Taxes to Collect         160           -      -

        Payment issued today in MXN at a rate => 1 MXN = 1.25 USD.
        Booked like:

            Bank                1160                   -      -
                Receivable              1160           -      -

        This does not generates any Exchange Rate Difference.

        But a Tax Cash Basis Entry is generated.
        Booked like:

            Tax Base Account    1000                   -      -
                Tax Base Account        1000           -      -
            Taxes to Collect     160                   -      -
                Taxes to Paid            160           -      -

        What I expect from here:
            - Base to report to DIOT if it would be the case (not in this
            case): * Tax Base Account MXN 1000.00
            - Paid to SAT MXN 160.00
            - Have no difference for Taxes to Collect
        """

        invoice_date = self.yesterday
        self.company.partner_id.write({
            'property_account_position_id': self.fiscal_position.id,
        })
        invoice_id = self.create_invoice(
            1000,
            invoice_date,
            currency_id=self.company.currency_id.id)

        self.assertEqual(invoice_id.state, "open")
        self.assertEqual(
            invoice_id.invoice_line_ids.invoice_line_tax_ids.
            l10n_mx_cfdi_tax_type, "Tasa")
        self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed",
                         invoice_id.message_ids.mapped('body'))

        xml = invoice_id.l10n_mx_edi_get_xml_etree()
        self.assertEqual(invoice_id.amount_total, float(xml.get('Total')),
                         "Total amount is not right")
        self.assertEqual(invoice_id.l10n_mx_edi_pac_status, "signed",
                         invoice_id.message_ids.mapped('body'))

        self.create_payment(
            invoice_id, self.today, 1160, self.bank_journal_mxn, self.mxn)  # noqa

        base_amls = self.account_move_line_model.search(
            [('account_id', '=', self.account_tax_cash_basis.id)])
        base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance'))
        self.assertEquals(
            float_round(base_at_payment, precision_digits=self.precision),
            -1000)

        tax_amls = self.account_move_line_model.search(
            [('account_id', '=', self.tax_account.id)])
        tax_diff = sum(tax_amls.mapped('balance'))
        self.assertEquals(
            float_round(tax_diff, precision_digits=self.precision), 0)
Пример #48
0
    def _create_invoice(self, move_vals):
        self.ensure_one()
        new_move = self.env['account.move'].sudo().with_company(self.company_id).with_context(default_move_type=move_vals['move_type']).create(move_vals)
        message = _("This invoice has been created from the point of sale session: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>") % (self.id, self.name)
        new_move.message_post(body=message)
        if self.config_id.cash_rounding:
            rounding_applied = float_round(self.amount_paid - self.amount_total,
                                           precision_rounding=new_move.currency_id.rounding)
            rounding_line = new_move.line_ids.filtered(lambda line: line.is_rounding_line)
            if rounding_applied:
                if rounding_applied > 0.0:
                    account_id = new_move.invoice_cash_rounding_id.loss_account_id.id
                else:
                    account_id = new_move.invoice_cash_rounding_id.profit_account_id.id
                if rounding_line:
                    if rounding_line.debit > 0:
                        rounding_line_difference = rounding_line.debit + rounding_applied
                    else:
                        rounding_line_difference = -rounding_line.credit + rounding_applied
                    if rounding_line_difference:
                        rounding_line.with_context(check_move_validity=False).write({
                            'debit': rounding_applied < 0.0 and -rounding_applied or 0.0,
                            'credit': rounding_applied > 0.0 and rounding_applied or 0.0,
                            'account_id': account_id,
                            'price_unit': rounding_applied,
                        })
                        existing_terms_line = new_move.line_ids.filtered(
                            lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
                        if existing_terms_line.debit > 0:
                            existing_terms_line_new_val = float_round(
                                existing_terms_line.debit + rounding_line_difference,
                                precision_rounding=new_move.currency_id.rounding)
                        else:
                            existing_terms_line_new_val = float_round(
                                -existing_terms_line.credit + rounding_line_difference,
                                precision_rounding=new_move.currency_id.rounding)
                        existing_terms_line.write({
                            'debit': existing_terms_line_new_val > 0.0 and existing_terms_line_new_val or 0.0,
                            'credit': existing_terms_line_new_val < 0.0 and -existing_terms_line_new_val or 0.0,
                        })

                        new_move._recompute_payment_terms_lines()

                else:
                    self.env['account.move.line'].create({
                        'debit': rounding_applied < 0.0 and -rounding_applied or 0.0,
                        'credit': rounding_applied > 0.0 and rounding_applied or 0.0,
                        'quantity': 1.0,
                        'amount_currency': rounding_applied,
                        'partner_id': new_move.partner_id.id,
                        'move_id': new_move.id,
                        'currency_id': new_move.currency_id if new_move.currency_id != new_move.company_id.currency_id else False,
                        'company_id': new_move.company_id.id,
                        'company_currency_id': new_move.company_id.currency_id.id,
                        'is_rounding_line': True,
                        'sequence': 9999,
                        'name': new_move.invoice_cash_rounding_id.name,
                        'account_id': account_id,
                    })
            else:
                if rounding_line:
                    rounding_line.unlink()
        return new_move
Пример #49
0
 def try_round(amount, expected, precision_rounding=None):
     value = float_round(amount, precision_rounding=precision_rounding)
     result = float_repr(value, precision_digits=2)
     self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected))
Пример #50
0
def float_round_custom(value, precision_digits=None, precision_rounding=None, rounding_method='HALF-UP'):
    result = float_round(value, precision_digits, precision_rounding, rounding_method)
    if precision_rounding == 1:
        return int(result)
    return result
Пример #51
0
    def test_14_cash_basis_multicurrency_creditnote_after_invoice(self):
        """Test to validate tax effectively receivable
        My company currency is MXN.

        Invoice issued two days ago in USD at a rate => 1MXN = 0.80 USD.
        Booked like:

            Receivable          1450                1160    USD
                Revenue                 1250       -1000    USD
                Taxes to Collect         200        -160    USD

        Credit Note issued today in USD at a rate => 1 MXN = 1.25 USD.
        Booked like:

            Revenue              800                1000    USD
            Taxes to Collect     128                 160    USD
                Receivable               928       -1160    USD

        This Generates a Exchange Rate Difference.
        Booked like:

            Loss Exchange rate   522                   0    USD
                Receivable               522           0    USD

        And two Tax Cash Basis Entry are generated.
        Booked like:

            Tax Base Account     800                1000    USD
                Tax Base Account         800       -1000    USD
            Taxes to Collect     128                 160    USD
                Taxes to Paid            128        -160    USD

            Tax Base Account     800                1000    USD
                Tax Base Account         800       -1000    USD
            Taxes to Paid        128                 160    USD
                Taxes to Collect         128        -160    USD

        What I expect from here:
            - Base to report to DIOT if it would be the case (not in this
            case): * Tax Base Account MXN 800.00 and MXN -800.00
            - Paid to SAT MXN 0.00
            - Have a difference of MXN -72.00 for Taxes to Collect that I would
            later have to issue as a Gain in Exchange Rate Difference

            Taxes to Collect      72                   0    USD
                Gain Exchange rate        72           0    USD
        """

        invoice_date = self.two_days_ago
        self.company.partner_id.write({
            'property_account_position_id': self.fiscal_position.id,
        })
        invoice_id = self.create_invoice(
            1000,
            invoice_date,
            currency_id=self.usd.id)

        self.assertEqual(invoice_id.state, "posted")

        refund = self.env['account.move.reversal'].with_context(active_ids=invoice_id.ids).create({
                'refund_method': 'refund',
                'reason': 'Refund Test',
                'date': self.today,
            })
        result = refund.reverse_moves()
        refund_id = result.get('domain')[1][2]
        refund = self.env['account.move'].browse(refund_id)
        refund.refresh()
        refund.post()
        self.assertEqual(refund.state, "open")

        ((invoice_id | refund)
         .mapped('move_id.line_ids')
         .filtered(lambda l: l.account_id.user_type_id.type == 'receivable')
         .reconcile())

        base_amls = self.account_move_line_model.search(
            [('account_id', '=', self.account_tax_cash_basis.id)])
        base_at_payment = sum(base_amls.filtered('tax_ids').mapped('balance'))
        self.assertEquals(
            float_round(base_at_payment, precision_digits=self.precision), 0)

        tax_amls = self.account_move_line_model.search(
            [('account_id', '=', self.tax_account.id)])
        tax_diff = sum(tax_amls.mapped('balance'))
        self.assertEquals(
            float_round(tax_diff, precision_digits=self.precision), -72)
Пример #52
0
 def _get_bom(self,
              bom_id=False,
              product_id=False,
              line_qty=False,
              line_id=False,
              level=False):
     bom = self.env['mrp.bom'].browse(bom_id)
     bom_quantity = line_qty
     if line_id:
         current_line = self.env['mrp.bom.line'].browse(int(line_id))
         bom_quantity = current_line.product_uom_id._compute_quantity(
             line_qty, bom.product_uom_id)
     # Display bom components for current selected product variant
     if product_id:
         product = self.env['product.product'].browse(int(product_id))
     else:
         product = bom.product_id or bom.product_tmpl_id.product_variant_id
     if product:
         attachments = self.env['mrp.document'].search([
             '|', '&', ('res_model', '=', 'product.product'),
             ('res_id', '=', product.id), '&',
             ('res_model', '=', 'product.template'),
             ('res_id', '=', product.product_tmpl_id.id)
         ])
     else:
         product = bom.product_tmpl_id
         attachments = self.env['mrp.document'].search([
             ('res_model', '=', 'product.template'),
             ('res_id', '=', product.id)
         ])
     operations = self._get_operation_line(
         bom.routing_id,
         float_round(bom_quantity / bom.product_qty,
                     precision_rounding=1,
                     rounding_method='UP'), 0)
     company = bom.company_id or self.env.company
     lines = {
         'bom':
         bom,
         'bom_qty':
         bom_quantity,
         'bom_prod_name':
         product.display_name,
         'currency':
         company.currency_id,
         'product':
         product,
         'code':
         bom and bom.display_name or '',
         'price':
         product.uom_id._compute_price(
             product.with_context(force_company=company.id).standard_price,
             bom.product_uom_id) * bom_quantity,
         'total':
         sum([op['total'] for op in operations]),
         'level':
         level or 0,
         'operations':
         operations,
         'operations_cost':
         sum([op['total'] for op in operations]),
         'attachments':
         attachments,
         'operations_time':
         sum([op['duration_expected'] for op in operations])
     }
     components, total = self._get_bom_lines(bom, bom_quantity, product,
                                             line_id, level)
     lines['components'] = components
     lines['total'] += total
     return lines
Пример #53
0
 def round_by_tools(amount):
     return tools.float_round(amount, precision_rounding=0.01)
Пример #54
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]
            if self.customer_type:
                if 'wholesale' in self.customer_type.name.lower():
                    if product.price_compute('ws_price')[product.id] > 0:
                        price = product.price_compute('ws_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.brand_id:
                    brand_ = product.product_tmpl_id.product_brand_id
                    while brand_:
                        if brand_.id == rule.brand_id.id:
                            break

                    if not brand_:
                        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
                    # reroute the computetion to wholesale price computation function if customer is wholesale
                    if self.customer_type and 'wholesale' in self.customer_type.name.lower(
                    ):
                        price = product.wholesaleprice_compute(
                            rule.base)[product.id]
                    else:
                        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
Пример #55
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}

        Date in context can be a date, datetime, ...

            :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.today()
        date = fields.Date.to_date(
            date)  # boundary conditions differ if we have a datetime
        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
            ]

        items = self._compute_price_rule_get_items(products_qty_partner, date,
                                                   uom_id, prod_tmpl_ids,
                                                   prod_ids, categ_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['uom.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['uom.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)], date,
                        uom_id)[product.id][0]  # TDE: 0 = price, 1 = rule
                    price = rule.base_pricelist_id.currency_id._convert(
                        price_tmp,
                        self.currency_id,
                        self.env.company,
                        date,
                        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':
                if suitable_rule.base == 'standard_price':
                    cur = product.cost_currency_id
                else:
                    cur = product.currency_id
                price = cur._convert(price,
                                     self.currency_id,
                                     self.env.company,
                                     date,
                                     round=False)

            if not suitable_rule:
                cur = product.currency_id
                price = cur._convert(price,
                                     self.currency_id,
                                     self.env.company,
                                     date,
                                     round=False)

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

        return results
Пример #56
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,
                                 bom_type='phantom')
            if bom:
                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
Пример #57
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))
Пример #58
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[
                                            'stock.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 {}
Пример #59
0
 def move_validate(self):
     ''' Validate moves based on a production order. '''
     moves = self._filter_closed_moves()
     quant_obj = self.env['stock.quant']
     moves_todo = self.env['stock.move']
     moves_to_unreserve = self.env['stock.move']
     # Create extra moves where necessary
     for move in moves:
         # Here, the `quantity_done` was already rounded to the product UOM by the `do_produce` wizard. However,
         # it is possible that the user changed the value before posting the inventory by a value that should be
         # rounded according to the move's UOM. In this specific case, we chose to round up the value, because it
         # is what is expected by the user (if i consumed/produced a little more, the whole UOM unit should be
         # consumed/produced and the moves are split correctly).
         rounding = move.product_uom.rounding
         move.quantity_done = float_round(move.quantity_done,
                                          precision_rounding=rounding,
                                          rounding_method='UP')
         if move.quantity_done <= 0:
             continue
         moves_todo |= move
         moves_todo |= move._create_extra_move()
     # Split moves where necessary and move quants
     for move in moves_todo:
         rounding = move.product_uom.rounding
         if float_compare(move.quantity_done,
                          move.product_uom_qty,
                          precision_rounding=rounding) < 0:
             # Need to do some kind of conversion here
             qty_split = move.product_uom._compute_quantity(
                 move.product_uom_qty - move.quantity_done,
                 move.product_id.uom_id)
             new_move = move.split(qty_split)
             # If you were already putting stock.move.lots on the next one in the work order, transfer those to the new move
             move.move_lot_ids.filtered(
                 lambda x: not x.done_wo or x.quantity_done == 0.0).write(
                     {'move_id': new_move})
             self.browse(new_move).quantity_done = 0.0
         main_domain = [('qty', '>', 0)]
         preferred_domain = [('reservation_id', '=', move.id)]
         fallback_domain = [('reservation_id', '=', False)]
         fallback_domain2 = [
             '&', ('reservation_id', '!=', move.id),
             ('reservation_id', '!=', False)
         ]
         preferred_domain_list = [preferred_domain] + [fallback_domain] + [
             fallback_domain2
         ]
         if move.has_tracking == 'none':
             quants = quant_obj.quants_get_preferred_domain(
                 move.product_qty,
                 move,
                 domain=main_domain,
                 preferred_domain_list=preferred_domain_list)
             self.env['stock.quant'].quants_move(quants, move,
                                                 move.location_dest_id)
         else:
             for movelot in move.move_lot_ids:
                 if float_compare(movelot.quantity_done,
                                  0,
                                  precision_rounding=rounding) > 0:
                     if not movelot.lot_id:
                         raise UserError(
                             _('You need to supply a lot/serial number.'))
                     qty = move.product_uom._compute_quantity(
                         movelot.quantity_done, move.product_id.uom_id)
                     quants = quant_obj.quants_get_preferred_domain(
                         qty,
                         move,
                         lot_id=movelot.lot_id.id,
                         domain=main_domain,
                         preferred_domain_list=preferred_domain_list)
                     self.env['stock.quant'].quants_move(
                         quants,
                         move,
                         move.location_dest_id,
                         lot_id=movelot.lot_id.id)
         moves_to_unreserve |= move
         # Next move in production order
         if move.move_dest_id:
             move.move_dest_id.action_assign()
     moves_to_unreserve.quants_unreserve()
     moves_todo.write({'state': 'done', 'date': fields.Datetime.now()})
     return moves_todo
Пример #60
0
    def validate_taxes_on_invoice(self):
        company = self.company_id
        Param = self.env['ir.config_parameter']
        api_id = Param.sudo().get_param(
            'account_taxcloud.taxcloud_api_id_{}'.format(company.id)
        ) or Param.sudo().get_param('account_taxcloud.taxcloud_api_id')
        api_key = Param.sudo().get_param(
            'account_taxcloud.taxcloud_api_key_{}'.format(company.id)
        ) or Param.sudo().get_param('account_taxcloud.taxcloud_api_key')
        request = TaxCloudRequest(api_id, api_key)

        shipper = self.company_id or self.env.user.company_id
        request.set_location_origin_detail(shipper)
        request.set_location_destination_detail(self._get_partner())

        request.set_invoice_items_detail(self)

        response = request.get_all_taxes_values()

        if response.get('error_message'):
            raise ValidationError(
                _('Unable to retrieve taxes from TaxCloud: ') + '\n' +
                response['error_message'] + '\n\n' +
                _('The configuration of TaxCloud is in the Accounting app, Settings menu.'
                  ))

        tax_values = response['values']

        raise_warning = False
        for index, line in enumerate(self.invoice_line_ids):
            if line.price_unit >= 0.0 and line.quantity >= 0.0:
                price = line.price_unit * (
                    1 - (line.discount or 0.0) / 100.0) * line.quantity
                if not price:
                    tax_rate = 0.0
                else:
                    tax_rate = tax_values[index] / price * 100
                if len(line.invoice_line_tax_ids) != 1 or float_compare(
                        line.invoice_line_tax_ids.amount,
                        tax_rate,
                        precision_digits=3):
                    raise_warning = True
                    tax_rate = float_round(tax_rate, precision_digits=3)
                    tax = self.env['account.tax'].sudo().search(
                        [('amount', '=', tax_rate),
                         ('amount_type', '=', 'percent'),
                         ('type_tax_use', '=', 'sale')],
                        limit=1)
                    if not tax:
                        tax = self.env['account.tax'].sudo().create({
                            'name':
                            'Tax %.3f %%' % (tax_rate),
                            'amount':
                            tax_rate,
                            'amount_type':
                            'percent',
                            'type_tax_use':
                            'sale',
                            'description':
                            'Sales Tax',
                        })
                    line.invoice_line_tax_ids = tax

        self._onchange_invoice_line_ids()

        if self.env.context.get('taxcloud_authorize_transaction'):
            current_date = fields.Datetime.context_timestamp(
                self, datetime.datetime.now())

            if self.type == 'out_invoice':
                request.client.service.AuthorizedWithCapture(
                    request.api_login_id,
                    request.api_key,
                    request.customer_id,
                    request.cart_id,
                    self.id,
                    current_date,  # DateAuthorized
                    current_date,  # DateCaptured
                )
            elif self.type == 'out_refund':
                request.set_invoice_items_detail(self)
                origin_invoice = self.env['account.invoice'].search(
                    [('number', '=', self.origin)], limit=1)
                if origin_invoice:
                    request.client.service.Returned(
                        request.api_login_id, request.api_key,
                        origin_invoice.id, request.cart_items,
                        fields.Datetime.from_string(self.date_invoice))
                else:
                    _logger.warning(
                        _("The source document on the refund is not valid and thus the refunded cart won't be logged on your taxcloud account"
                          ))

        if raise_warning:
            return {
                'warning':
                _('The tax rates have been updated, you may want to check it before validation'
                  )
            }
        else:
            return True