Пример #1
0
    def _action_launch_stock_rule(self):
        """
        Launch procurement group run method with required/custom fields genrated by a
        sale order line. procurement group will launch '_run_pull', '_run_buy' or '_run_manufacture'
        depending on the sale order line product rule.
        """
        precision = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        errors = []
        for line in self:
            if line.state != 'sale' or not line.product_id.type in ('consu',
                                                                    'product'):
                continue
            qty = line._get_qty_procurement()
            if float_compare(qty,
                             line.product_uom_qty,
                             precision_digits=precision) >= 0:
                continue

            group_id = line.order_id.procurement_group_id
            if not group_id:
                group_id = self.env['procurement.group'].create({
                    'name':
                    line.order_id.name,
                    'move_type':
                    line.order_id.picking_policy,
                    'sale_id':
                    line.order_id.id,
                    'partner_id':
                    line.order_id.partner_shipping_id.id,
                })
                line.order_id.procurement_group_id = group_id
            else:
                # In case the procurement group is already created and the order was
                # cancelled, we need to update certain values of the group.
                updated_vals = {}
                if group_id.partner_id != line.order_id.partner_shipping_id:
                    updated_vals.update(
                        {'partner_id': line.order_id.partner_shipping_id.id})
                if group_id.move_type != line.order_id.picking_policy:
                    updated_vals.update(
                        {'move_type': line.order_id.picking_policy})
                if updated_vals:
                    group_id.write(updated_vals)

            values = line._prepare_procurement_values(group_id=group_id)
            product_qty = line.product_uom_qty - qty

            procurement_uom = line.product_uom
            quant_uom = line.product_id.uom_id
            get_param = self.env['ir.config_parameter'].sudo().get_param
            if procurement_uom.id != quant_uom.id and get_param(
                    'stock.propagate_uom') != '1':
                product_qty = line.product_uom._compute_quantity(
                    product_qty, quant_uom, rounding_method='HALF-UP')
                procurement_uom = quant_uom

            try:
                self.env['procurement.group'].run(
                    line.product_id, product_qty, procurement_uom,
                    line.order_id.partner_shipping_id.property_stock_customer,
                    line.name, line.order_id.name, values)
            except UserError as error:
                errors.append(error.name)
        if errors:
            raise UserError('\n'.join(errors))
        return True
Пример #2
0
 def unlink(self):
     if 'done' in self.mapped('state'):
         raise UserError(_("You cannot delete an unbuild order if the state is 'Done'."))
     return super(MrpUnbuild, self).unlink()
Пример #3
0
 def unlink(self):
     for order in self:
         if not order.state == 'cancel':
             raise UserError(_('In order to delete a purchase order, you must cancel it first.'))
     return super(PurchaseOrder, self).unlink()
Пример #4
0
    def generate_email(self, res_ids, fields=None):
        """Generates an email from the template for given the given model based on
        records given by res_ids.

        :param res_id: id of the record to use for rendering the template (model
                       is taken from template definition)
        :returns: a dict containing all relevant fields for creating a new
                  mail.mail entry, with one extra key ``attachments``, in the
                  format [(report_name, data)] where data is base64 encoded.
        """
        self.ensure_one()
        multi_mode = True
        if isinstance(res_ids, int):
            res_ids = [res_ids]
            multi_mode = False
        if fields is None:
            fields = [
                'subject', 'body_html', 'email_from', 'email_to', 'partner_to',
                'email_cc', 'reply_to', 'scheduled_date'
            ]

        res_ids_to_templates = self.get_email_template(res_ids)

        # templates: res_id -> template; template -> res_ids
        templates_to_res_ids = {}
        for res_id, template in res_ids_to_templates.items():
            templates_to_res_ids.setdefault(template, []).append(res_id)

        results = dict()
        for template, template_res_ids in templates_to_res_ids.items():
            Template = self.env['mail.template']
            # generate fields value for all res_ids linked to the current template
            if template.lang:
                Template = Template.with_context(
                    lang=template._context.get('lang'))
            for field in fields:
                Template = Template.with_context(safe=field in {'subject'})
                generated_field_values = Template._render_template(
                    getattr(template, field),
                    template.model,
                    template_res_ids,
                    post_process=(field == 'body_html'))
                for res_id, field_value in generated_field_values.items():
                    results.setdefault(res_id, dict())[field] = field_value
            # compute recipients
            if any(field in fields
                   for field in ['email_to', 'partner_to', 'email_cc']):
                results = template.generate_recipients(results,
                                                       template_res_ids)
            # update values for all res_ids
            for res_id in template_res_ids:
                values = results[res_id]
                # body: add user signature, sanitize
                if 'body_html' in fields and template.user_signature:
                    signature = self.env.user.signature
                    if signature:
                        values['body_html'] = tools.append_content_to_html(
                            values['body_html'], signature, plaintext=False)
                if values.get('body_html'):
                    values['body'] = tools.html_sanitize(values['body_html'])
                # technical settings
                values.update(
                    mail_server_id=template.mail_server_id.id or False,
                    auto_delete=template.auto_delete,
                    model=template.model,
                    res_id=res_id or False,
                    attachment_ids=[
                        attach.id for attach in template.attachment_ids
                    ],
                )

            # Add report in attachments: generate once for all template_res_ids
            if template.report_template:
                for res_id in template_res_ids:
                    attachments = []
                    report_name = self._render_template(
                        template.report_name, template.model, res_id)
                    report = template.report_template
                    report_service = report.report_name

                    if report.report_type in ['qweb-html', 'qweb-pdf']:
                        result, format = report.render_qweb_pdf([res_id])
                    else:
                        res = report.render([res_id])
                        if not res:
                            raise UserError(
                                _('Unsupported report type %s found.') %
                                report.report_type)
                        result, format = res

                    # TODO in trunk, change return format to binary to match message_post expected format
                    result = base64.b64encode(result)
                    if not report_name:
                        report_name = 'report.' + report_service
                    ext = "." + format
                    if not report_name.endswith(ext):
                        report_name += ext
                    attachments.append((report_name, result))
                    results[res_id]['attachments'] = attachments

        return multi_mode and results or results[res_ids[0]]
Пример #5
0
 def write(self, vals):
     res = super(slider,self).write(vals)
     if self.slider_type=='product' and not self.slider_filter_ids:
         raise UserError(_('Sorry! Please set product filters first'))
     else:
         return res
Пример #6
0
 def unlink(self):
     if self.filtered(lambda categ: categ.measure_type == 'time'):
         raise UserError(
             _("You cannot delete this UoM Category as it is used by the system."
               ))
     return super(UoMCategory, self).unlink()
Пример #7
0
    def _render_template(self,
                         template_txt,
                         model,
                         res_ids,
                         post_process=False):
        """Override to add website to context"""
        multi_mode = True
        if isinstance(res_ids, pycompat.integer_types):
            multi_mode = False
            res_ids = [res_ids]

        results = dict.fromkeys(res_ids, u"")

        # try to load the template
        try:
            mako_env = mako_safe_template_env if self.env.context.get(
                'safe') else mako_template_env
            template = mako_env.from_string(tools.ustr(template_txt))
        except Exception:
            _logger.info("Failed to load template %r",
                         template_txt,
                         exc_info=True)
            return multi_mode and results or results[res_ids[0]]

        # prepare template variables
        records = self.env[model].browse(
            it for it in res_ids if it)  # filter to avoid browsing [None]
        res_to_rec = dict.fromkeys(res_ids, None)
        for record in records:
            res_to_rec[record.id] = record

        variables = {
            'format_date':
            lambda date, format=False, context=self._context: format_date(
                self.env, date, format),
            'format_tz':
            lambda dt, tz=False, format=False, context=self._context:
            format_tz(self.env, dt, tz, format),
            'format_amount':
            lambda amount, currency, context=self._context: format_amount(
                self.env, amount, currency),
            'user':
            self.env.user,
            'ctx':
            self._context,  # context kw would clash with mako internals
        }

        # [NEW] Check website and company context
        company = self.env['res.company']  # empty value

        company_id = self.env.context.get('force_company')
        if company_id:
            company = self.env['res.company'].sudo().browse(company_id)

        if self.env.context.get('website_id'):
            website = self.env['website'].browse(
                self.env.context.get('website_id'))
        else:
            website = self.env.user.backend_website_id

        for res_id, record in res_to_rec.items():
            record_company = company
            if not record_company:
                if hasattr(record, 'company_id') and record.company_id:
                    record_company = record.company_id

            record_website = website
            if hasattr(record, 'website_id') and record.website_id:
                record_website = record.website_id

            if record_company and record_website \
               and record_website.company_id != company:
                # company and website are incompatible, so keep only company
                record_website = self.env['website']  # empty value

            record_context = dict(force_company=record_company.id,
                                  website_id=record_website.id)
            variables['object'] = record.with_context(**record_context)
            variables['website'] = record_website

            try:
                render_result = template.render(variables)
            except Exception:
                _logger.info("Failed to render template %r using values %r" %
                             (template, variables),
                             exc_info=True)
                raise UserError(
                    _("Failed to render template %r using values %r") %
                    (template, variables))
            if render_result == u"False":
                render_result = u""

            if post_process:
                render_result = self.with_context(
                    **record_context).render_post_process(render_result)

            results[res_id] = render_result

        return multi_mode and results or results[res_ids[0]]
Пример #8
0
    def connect(self,
                host=None,
                port=None,
                user=None,
                password=None,
                encryption=None,
                smtp_debug=False,
                mail_server_id=None):
        """Returns a new SMTP connection to the given SMTP server.
           When running in test mode, this method does nothing and returns `None`.

           :param host: host or IP of SMTP server to connect to, if mail_server_id not passed
           :param int port: SMTP port to connect to
           :param user: optional username to authenticate with
           :param password: optional password to authenticate with
           :param string encryption: optional, ``'ssl'`` | ``'starttls'``
           :param bool smtp_debug: toggle debugging of SMTP sessions (all i/o
                              will be output in logs)
           :param mail_server_id: ID of specific mail server to use (overrides other parameters)
        """
        # Do not actually connect while running in test mode
        if getattr(threading.currentThread(), 'testing', False):
            return None

        mail_server = smtp_encryption = None
        if mail_server_id:
            mail_server = self.sudo().browse(mail_server_id)
        elif not host:
            mail_server = self.sudo().search([], order='sequence', limit=1)

        if mail_server:
            smtp_server = mail_server.smtp_host
            smtp_port = mail_server.smtp_port
            smtp_user = mail_server.smtp_user
            smtp_password = mail_server.smtp_pass
            smtp_encryption = mail_server.smtp_encryption
            smtp_debug = smtp_debug or mail_server.smtp_debug
        else:
            # we were passed individual smtp parameters or nothing and there is no default server
            smtp_server = host or tools.config.get('smtp_server')
            smtp_port = tools.config.get('smtp_port',
                                         25) if port is None else port
            smtp_user = user or tools.config.get('smtp_user')
            smtp_password = password or tools.config.get('smtp_password')
            smtp_encryption = encryption
            if smtp_encryption is None and tools.config.get('smtp_ssl'):
                smtp_encryption = 'starttls'  # smtp_ssl => STARTTLS as of v7

        if not smtp_server:
            raise UserError((_("Missing SMTP Server") + "\n" +
                             _("Please define at least one SMTP server, "
                               "or provide the SMTP parameters explicitly.")))

        if smtp_encryption == 'ssl':
            if 'SMTP_SSL' not in smtplib.__all__:
                raise UserError(
                    _("Your Eagle Server does not support SMTP-over-SSL. "
                      "You could use STARTTLS instead. "
                      "If SSL is needed, an upgrade to Python 2.6 on the server-side "
                      "should do the trick."))
            connection = smtplib.SMTP_SSL(smtp_server,
                                          smtp_port,
                                          timeout=SMTP_TIMEOUT)
        else:
            connection = smtplib.SMTP(smtp_server,
                                      smtp_port,
                                      timeout=SMTP_TIMEOUT)
        connection.set_debuglevel(smtp_debug)
        if smtp_encryption == 'starttls':
            # starttls() will perform ehlo() if needed first
            # and will discard the previous list of services
            # after successfully performing STARTTLS command,
            # (as per RFC 3207) so for example any AUTH
            # capability that appears only on encrypted channels
            # will be correctly detected for next step
            connection.starttls()

        if smtp_user:
            # Attempt authentication - will raise if AUTH service not supported
            # The user/password must be converted to bytestrings in order to be usable for
            # certain hashing schemes, like HMAC.
            # See also bug #597143 and python issue #5285
            smtp_user = pycompat.to_native(ustr(smtp_user))
            smtp_password = pycompat.to_native(ustr(smtp_password))
            connection.login(smtp_user, smtp_password)

        # Some methods of SMTP don't check whether EHLO/HELO was sent.
        # Anyway, as it may have been sent by login(), all subsequent usages should consider this command as sent.
        connection.ehlo_or_helo_if_needed()

        return connection
Пример #9
0
    def action_create_sale_order(self):
        # if project linked to SO line or at least on tasks with SO line, then we consider project as billable.
        if self.project_id.sale_line_id:
            raise UserError(
                _("The project is already linked to a sales order item."))

        if self.billable_type == 'employee_rate':
            # at least one line
            if not self.line_ids:
                raise UserError(_("At least one line should be filled."))

            # all employee having timesheet should be in the wizard map
            timesheet_employees = self.env['account.analytic.line'].search([
                ('task_id', 'in', self.project_id.tasks.ids)
            ]).mapped('employee_id')
            map_employees = self.line_ids.mapped('employee_id')
            missing_meployees = timesheet_employees - map_employees
            if missing_meployees:
                raise UserError(
                    _('The Sales Order cannot be created because you did not enter some employees that entered timesheets on this project. Please list all the relevant employees before creating the Sales Order.\nMissing employee(s): %s'
                      ) % (', '.join(missing_meployees.mapped('name'))))

        # check here if timesheet already linked to SO line
        timesheet_with_so_line = self.env[
            'account.analytic.line'].search_count([('task_id', 'in',
                                                    self.project_id.tasks.ids),
                                                   ('so_line', '!=', False)])
        if timesheet_with_so_line:
            raise UserError(
                _('The sales order cannot be created because some timesheets of this project are already linked to another sales order.'
                  ))

        # create SO
        sale_order = self.env['sale.order'].create({
            'partner_id':
            self.partner_id.id,
            'analytic_account_id':
            self.project_id.analytic_account_id.id,
            'client_order_ref':
            self.project_id.name,
        })
        sale_order.onchange_partner_id()
        sale_order.onchange_partner_shipping_id()

        # create the sale lines, the map (optional), and assign existing timesheet to sale lines
        if self.billable_type == 'project_rate':
            self._make_billable_at_project_rate(sale_order)
        else:
            self._make_billable_at_employee_rate(sale_order)

        # confirm SO
        sale_order.action_confirm()

        view_form_id = self.env.ref('sale.view_order_form').id
        action = self.env.ref('sale.action_orders').read()[0]
        action.update({
            'views': [(view_form_id, 'form')],
            'view_mode': 'form',
            'name': sale_order.name,
            'res_id': sale_order.id,
        })
        return action
Пример #10
0
    def _import_xml_invoice(self, tree):
        ''' Extract invoice values from the E-Invoice xml tree passed as parameter.

        :param content: The tree of the xml file.
        :return: A dictionary containing account.invoice values to create/update it.
        '''

        invoices = self.env['account.move']
        multi = False

        # possible to have multiple invoices in the case of an invoice batch, the batch itself is repeated for every invoice of the batch
        for body_tree in tree.xpath('//FatturaElettronicaBody',
                                    namespaces=tree.nsmap):
            if multi:
                # make sure all the iterations create a new invoice record (except the first which could have already created one)
                self = self.env['account.move']
            multi = True

            elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento',
                                  namespaces=tree.nsmap)
            if elements and elements[0].text and elements[0].text == 'TD01':
                self_ctx = self.with_context(default_type='in_invoice')
            elif elements and elements[0].text and elements[0].text == 'TD04':
                self_ctx = self.with_context(default_type='in_refund')
            else:
                _logger.info(
                    _('Document type not managed: %s.') % (elements[0].text))

            # type must be present in the context to get the right behavior of the _default_journal method (account.move).
            # journal_id must be present in the context to get the right behavior of the _default_account method (account.move.line).

            elements = tree.xpath('//CessionarioCommittente//IdCodice',
                                  namespaces=tree.nsmap)
            company = elements and self.env['res.company'].search(
                [('vat', 'ilike', elements[0].text)], limit=1)
            if not company:
                elements = tree.xpath(
                    '//CessionarioCommittente//CodiceFiscale',
                    namespaces=tree.nsmap)
                company = elements and self.env['res.company'].search(
                    [('l10n_it_codice_fiscale', 'ilike', elements[0].text)],
                    limit=1)

            if company:
                self_ctx = self_ctx.with_context(company_id=company.id)
            else:
                company = self.env.company
                if elements:
                    _logger.info(
                        _('Company not found with codice fiscale: %s. The company\'s user is set by default.'
                          ) % elements[0].text)
                else:
                    _logger.info(
                        _('Company not found. The company\'s user is set by default.'
                          ))

            if not self.env.is_superuser():
                if self.env.company != company:
                    raise UserError(
                        _("You can only import invoice concern your current company: %s"
                          ) % self.env.company.display_name)

            # Refund type.
            # TD01 == invoice
            # TD02 == advance/down payment on invoice
            # TD03 == advance/down payment on fee
            # TD04 == credit note
            # TD05 == debit note
            # TD06 == fee
            elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento',
                                  namespaces=tree.nsmap)
            if elements and elements[0].text and elements[0].text == 'TD01':
                type = 'in_invoice'
            elif elements and elements[0].text and elements[0].text == 'TD04':
                type = 'in_refund'
            # self could be a single record (editing) or be empty (new).
            with Form(self.with_context(default_type=type)) as invoice_form:
                message_to_log = []

                # Partner (first step to avoid warning 'Warning! You must first select a partner.'). <1.2>
                elements = tree.xpath('//CedentePrestatore//IdCodice',
                                      namespaces=tree.nsmap)
                partner = elements and self.env['res.partner'].search([
                    '&', ('vat', 'ilike', elements[0].text), '|',
                    ('company_id', '=', company.id), ('company_id', '=', False)
                ],
                                                                      limit=1)
                if not partner:
                    elements = tree.xpath('//CedentePrestatore//CodiceFiscale',
                                          namespaces=tree.nsmap)
                    partner = elements and self.env['res.partner'].search(
                        [
                            '&',
                            ('l10n_it_codice_fiscale', '=', elements[0].text),
                            '|', ('company_id', '=', company.id),
                            ('company_id', '=', False)
                        ],
                        limit=1)
                if not partner:
                    elements = tree.xpath('//DatiTrasmissione//Email',
                                          namespaces=tree.nsmap)
                    partner = elements and self.env['res.partner'].search(
                        [
                            '&', '|', ('email', '=', elements[0].text),
                            ('l10n_it_pec_email', '=', elements[0].text), '|',
                            ('company_id', '=', company.id),
                            ('company_id', '=', False)
                        ],
                        limit=1)
                if partner:
                    invoice_form.partner_id = partner
                else:
                    message_to_log.append("%s<br/>%s" % (_(
                        "Vendor not found, useful informations from XML file:"
                    ), self._compose_info_message(tree,
                                                  './/CedentePrestatore')))

                # Numbering attributed by the transmitter. <1.1.2>
                elements = tree.xpath('//ProgressivoInvio',
                                      namespaces=tree.nsmap)
                if elements:
                    invoice_form.invoice_payment_ref = elements[0].text

                elements = body_tree.xpath('.//DatiGeneraliDocumento//Numero',
                                           namespaces=body_tree.nsmap)
                if elements:
                    invoice_form.ref = elements[0].text

                # Currency. <2.1.1.2>
                elements = body_tree.xpath('.//DatiGeneraliDocumento/Divisa',
                                           namespaces=body_tree.nsmap)
                if elements:
                    currency_str = elements[0].text
                    currency = self.env.ref('base.%s' % currency_str.upper(),
                                            raise_if_not_found=False)
                    if currency != self.env.company.currency_id and currency.active:
                        invoice_form.currency_id = currency

                # Date. <2.1.1.3>
                elements = body_tree.xpath('.//DatiGeneraliDocumento/Data',
                                           namespaces=body_tree.nsmap)
                if elements:
                    date_str = elements[0].text
                    date_obj = datetime.strptime(
                        date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
                    invoice_form.invoice_date = date_obj.strftime(
                        DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)

                #  Dati Bollo. <2.1.1.6>
                elements = body_tree.xpath(
                    './/DatiGeneraliDocumento/DatiBollo/ImportoBollo',
                    namespaces=body_tree.nsmap)
                if elements:
                    invoice_form.l10n_it_stamp_duty = float(elements[0].text)

                # List of all amount discount (will be add after all article to avoid to have a negative sum)
                discount_list = []
                percentage_global_discount = 1.0

                # Global discount. <2.1.1.8>
                discount_elements = body_tree.xpath(
                    './/DatiGeneraliDocumento/ScontoMaggiorazione',
                    namespaces=body_tree.nsmap)
                total_discount_amount = 0.0
                if discount_elements:
                    for discount_element in discount_elements:
                        discount_line = discount_element.xpath(
                            './/Tipo', namespaces=body_tree.nsmap)
                        discount_sign = -1
                        if discount_line and discount_line[0].text == 'SC':
                            discount_sign = 1
                        discount_percentage = discount_element.xpath(
                            './/Percentuale', namespaces=body_tree.nsmap)
                        if discount_percentage and discount_percentage[0].text:
                            percentage_global_discount *= 1 - float(
                                discount_percentage[0].text
                            ) / 100 * discount_sign

                        discount_amount_text = discount_element.xpath(
                            './/Importo', namespaces=body_tree.nsmap)
                        if discount_amount_text and discount_amount_text[
                                0].text:
                            discount_amount = float(discount_amount_text[0].
                                                    text) * discount_sign * -1
                            discount = {}
                            discount["seq"] = 0

                            if discount_amount < 0:
                                discount["name"] = _('GLOBAL DISCOUNT')
                            else:
                                discount["name"] = _('GLOBAL EXTRA CHARGE')
                            discount["amount"] = discount_amount
                            discount["tax"] = []
                            discount_list.append(discount)

                # Comment. <2.1.1.11>
                elements = body_tree.xpath('.//DatiGeneraliDocumento//Causale',
                                           namespaces=body_tree.nsmap)
                for element in elements:
                    invoice_form.narration = '%s%s\n' % (invoice_form.narration
                                                         or '', element.text)

                # Informations relative to the purchase order, the contract, the agreement,
                # the reception phase or invoices previously transmitted
                # <2.1.2> - <2.1.6>
                for document_type in [
                        'DatiOrdineAcquisto', 'DatiContratto',
                        'DatiConvenzione', 'DatiRicezione',
                        'DatiFattureCollegate'
                ]:
                    elements = body_tree.xpath('.//DatiGenerali/' +
                                               document_type,
                                               namespaces=body_tree.nsmap)
                    if elements:
                        for element in elements:
                            message_to_log.append(
                                "%s %s<br/>%s" %
                                (document_type, _("from XML file:"),
                                 self._compose_info_message(element, '.')))

                #  Dati DDT. <2.1.8>
                elements = body_tree.xpath('.//DatiGenerali/DatiDDT',
                                           namespaces=body_tree.nsmap)
                if elements:
                    message_to_log.append(
                        "%s<br/>%s" %
                        (_("Transport informations from XML file:"),
                         self._compose_info_message(
                             body_tree, './/DatiGenerali/DatiDDT')))

                # Due date. <2.4.2.5>
                elements = body_tree.xpath(
                    './/DatiPagamento/DettaglioPagamento/DataScadenzaPagamento',
                    namespaces=body_tree.nsmap)
                if elements:
                    date_str = elements[0].text
                    date_obj = datetime.strptime(
                        date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
                    invoice_form.invoice_date_due = fields.Date.to_string(
                        date_obj)

                # Total amount. <2.4.2.6>
                elements = body_tree.xpath('.//ImportoPagamento',
                                           namespaces=body_tree.nsmap)
                amount_total_import = 0
                for element in elements:
                    amount_total_import += float(element.text)
                if amount_total_import:
                    message_to_log.append(
                        _("Total amount from the XML File: %s") %
                        (amount_total_import))

                # Bank account. <2.4.2.13>
                elements = body_tree.xpath(
                    './/DatiPagamento/DettaglioPagamento/IBAN',
                    namespaces=body_tree.nsmap)
                if elements:
                    if invoice_form.partner_id and invoice_form.partner_id.commercial_partner_id:
                        bank = self.env['res.partner.bank'].search([
                            ('acc_number', '=', elements[0].text),
                            ('partner_id.id', '=',
                             invoice_form.partner_id.commercial_partner_id.id)
                        ])
                    else:
                        bank = self.env['res.partner.bank'].search([
                            ('acc_number', '=', elements[0].text)
                        ])
                    if bank:
                        invoice_form.invoice_partner_bank_id = bank
                    else:
                        message_to_log.append("%s<br/>%s" % (
                            _("Bank account not found, useful informations from XML file:"
                              ),
                            self._compose_multi_info_message(
                                body_tree, [
                                    './/DatiPagamento//Beneficiario',
                                    './/DatiPagamento//IstitutoFinanziario',
                                    './/DatiPagamento//IBAN',
                                    './/DatiPagamento//ABI',
                                    './/DatiPagamento//CAB',
                                    './/DatiPagamento//BIC',
                                    './/DatiPagamento//ModalitaPagamento'
                                ])))
                else:
                    elements = body_tree.xpath(
                        './/DatiPagamento/DettaglioPagamento',
                        namespaces=body_tree.nsmap)
                    if elements:
                        message_to_log.append("%s<br/>%s" % (
                            _("Bank account not found, useful informations from XML file:"
                              ),
                            self._compose_info_message(body_tree,
                                                       './/DatiPagamento')))

                # Invoice lines. <2.2.1>
                elements = body_tree.xpath('.//DettaglioLinee',
                                           namespaces=body_tree.nsmap)
                if elements:
                    for element in elements:
                        with invoice_form.invoice_line_ids.new(
                        ) as invoice_line_form:

                            # Sequence.
                            line_elements = element.xpath(
                                './/NumeroLinea', namespaces=body_tree.nsmap)
                            if line_elements:
                                invoice_line_form.sequence = int(
                                    line_elements[0].text) * 2

                            # Product.
                            line_elements = element.xpath(
                                './/Descrizione', namespaces=body_tree.nsmap)
                            if line_elements:
                                invoice_line_form.name = " ".join(
                                    line_elements[0].text.split())

                            elements_code = element.xpath(
                                './/CodiceArticolo',
                                namespaces=body_tree.nsmap)
                            if elements_code:
                                for element_code in elements_code:
                                    type_code = element_code.xpath(
                                        './/CodiceTipo',
                                        namespaces=body_tree.nsmap)[0]
                                    code = element_code.xpath(
                                        './/CodiceValore',
                                        namespaces=body_tree.nsmap)[0]
                                    if type_code.text == 'EAN':
                                        product = self.env[
                                            'product.product'].search([
                                                ('barcode', '=', code.text)
                                            ])
                                        if product:
                                            invoice_line_form.product_id = product
                                            break
                                    if partner:
                                        product_supplier = self.env[
                                            'product.supplierinfo'].search([
                                                ('name', '=', partner.id),
                                                ('product_code', '=',
                                                 code.text)
                                            ])
                                        if product_supplier and product_supplier.product_id:
                                            invoice_line_form.product_id = product_supplier.product_id
                                            break
                                if not invoice_line_form.product_id:
                                    for element_code in elements_code:
                                        code = element_code.xpath(
                                            './/CodiceValore',
                                            namespaces=body_tree.nsmap)[0]
                                        product = self.env[
                                            'product.product'].search([
                                                ('default_code', '=',
                                                 code.text)
                                            ])
                                        if product:
                                            invoice_line_form.product_id = product
                                            break

                            # Price Unit.
                            line_elements = element.xpath(
                                './/PrezzoUnitario',
                                namespaces=body_tree.nsmap)
                            if line_elements:
                                invoice_line_form.price_unit = float(
                                    line_elements[0].text)

                            # Quantity.
                            line_elements = element.xpath(
                                './/Quantita', namespaces=body_tree.nsmap)
                            if line_elements:
                                invoice_line_form.quantity = float(
                                    line_elements[0].text)
                            else:
                                invoice_line_form.quantity = 1

                            # Taxes
                            tax_element = element.xpath(
                                './/AliquotaIVA', namespaces=body_tree.nsmap)
                            natura_element = element.xpath(
                                './/Natura', namespaces=body_tree.nsmap)
                            invoice_line_form.tax_ids.clear()
                            if tax_element and tax_element[0].text:
                                percentage = float(tax_element[0].text)
                                if natura_element and natura_element[0].text:
                                    l10n_it_kind_exoneration = natura_element[
                                        0].text
                                    tax = self.env['account.tax'].search(
                                        [
                                            ('company_id', '=',
                                             invoice_form.company_id.id),
                                            ('amount_type', '=', 'percent'),
                                            ('type_tax_use', '=', 'purchase'),
                                            ('amount', '=', percentage),
                                            ('l10n_it_kind_exoneration', '=',
                                             l10n_it_kind_exoneration),
                                        ],
                                        limit=1)
                                else:
                                    tax = self.env['account.tax'].search(
                                        [
                                            ('company_id', '=',
                                             invoice_form.company_id.id),
                                            ('amount_type', '=', 'percent'),
                                            ('type_tax_use', '=', 'purchase'),
                                            ('amount', '=', percentage),
                                        ],
                                        limit=1)
                                    l10n_it_kind_exoneration = ''

                                if tax:
                                    invoice_line_form.tax_ids.add(tax)
                                else:
                                    if l10n_it_kind_exoneration:
                                        message_to_log.append(
                                            _("Tax not found with percentage: %s and exoneration %s for the article: %s"
                                              ) % (percentage,
                                                   l10n_it_kind_exoneration,
                                                   invoice_line_form.name))
                                    else:
                                        message_to_log.append(
                                            _("Tax not found with percentage: %s for the article: %s"
                                              ) % (percentage,
                                                   invoice_line_form.name))

                            # Discount in cascade mode.
                            # if 3 discounts : -10% -50€ -20%
                            # the result must be : (((price -10%)-50€) -20%)
                            # Generic form : (((price -P1%)-A1€) -P2%)
                            # It will be split in two parts: fix amount and pourcent amount
                            # example: (((((price - A1€) -P2%) -A3€) -A4€) -P5€)
                            # pourcent: 1-(1-P2)*(1-P5)
                            # fix amount: A1*(1-P2)*(1-P5)+A3*(1-P5)+A4*(1-P5) (we must take account of all
                            # percentage present after the fix amount)
                            line_elements = element.xpath(
                                './/ScontoMaggiorazione',
                                namespaces=body_tree.nsmap)
                            total_discount_amount = 0.0
                            total_discount_percentage = percentage_global_discount
                            if line_elements:
                                for line_element in line_elements:
                                    discount_line = line_element.xpath(
                                        './/Tipo', namespaces=body_tree.nsmap)
                                    discount_sign = -1
                                    if discount_line and discount_line[
                                            0].text == 'SC':
                                        discount_sign = 1
                                    discount_percentage = line_element.xpath(
                                        './/Percentuale',
                                        namespaces=body_tree.nsmap)
                                    if discount_percentage and discount_percentage[
                                            0].text:
                                        pourcentage_actual = 1 - float(
                                            discount_percentage[0].text
                                        ) / 100 * discount_sign
                                        total_discount_percentage *= pourcentage_actual
                                        total_discount_amount *= pourcentage_actual

                                    discount_amount = line_element.xpath(
                                        './/Importo',
                                        namespaces=body_tree.nsmap)
                                    if discount_amount and discount_amount[
                                            0].text:
                                        total_discount_amount += float(
                                            discount_amount[0].text
                                        ) * discount_sign * -1

                                # Save amount discount.
                                if total_discount_amount != 0:
                                    discount = {}
                                    discount[
                                        "seq"] = invoice_line_form.sequence + 1

                                    if total_discount_amount < 0:
                                        discount["name"] = _(
                                            'DISCOUNT: '
                                        ) + invoice_line_form.name
                                    else:
                                        discount["name"] = _(
                                            'EXTRA CHARGE: '
                                        ) + invoice_line_form.name
                                    discount["amount"] = total_discount_amount
                                    discount["tax"] = []
                                    for tax in invoice_line_form.tax_ids:
                                        discount["tax"].append(tax)
                                    discount_list.append(discount)
                            invoice_line_form.discount = (
                                1 - total_discount_percentage) * 100

                # Apply amount discount.
                for discount in discount_list:
                    with invoice_form.invoice_line_ids.new(
                    ) as invoice_line_form_discount:
                        invoice_line_form_discount.tax_ids.clear()
                        invoice_line_form_discount.sequence = discount["seq"]
                        invoice_line_form_discount.name = discount["name"]
                        invoice_line_form_discount.price_unit = discount[
                            "amount"]

            new_invoice = invoice_form.save()
            new_invoice.l10n_it_send_state = "other"

            elements = body_tree.xpath('.//Allegati',
                                       namespaces=body_tree.nsmap)
            if elements:
                for element in elements:
                    name_attachment = element.xpath(
                        './/NomeAttachment',
                        namespaces=body_tree.nsmap)[0].text
                    attachment_64 = str.encode(
                        element.xpath('.//Attachment',
                                      namespaces=body_tree.nsmap)[0].text)
                    attachment_64 = self.env['ir.attachment'].create({
                        'name':
                        name_attachment,
                        'datas':
                        attachment_64,
                        'type':
                        'binary',
                    })

                    # default_res_id is had to context to avoid facturx to import his content
                    # no_new_invoice to prevent from looping on the message_post that would create a new invoice without it
                    new_invoice.with_context(
                        no_new_invoice=True,
                        default_res_id=new_invoice.id).message_post(
                            body=(_("Attachment from XML")),
                            attachment_ids=[attachment_64.id])

            for message in message_to_log:
                new_invoice.message_post(body=message)

            invoices += new_invoice
        return invoices
Пример #11
0
    def _check_before_xml_exporting(self):
        seller = self.company_id
        buyer = self.commercial_partner_id

        # <1.1.1.1>
        if not seller.country_id:
            raise UserError(
                _("%s must have a country") % (seller.display_name))

        # <1.1.1.2>
        if not seller.vat:
            raise UserError(
                _("%s must have a VAT number") % (seller.display_name))
        elif len(seller.vat) > 30:
            raise UserError(
                _("The maximum length for VAT number is 30. %s have a VAT number too long: %s."
                  ) % (seller.display_name, seller.vat))

        # <1.2.1.2>
        if not seller.l10n_it_codice_fiscale:
            raise UserError(
                _("%s must have a codice fiscale number") %
                (seller.display_name))

        # <1.2.1.8>
        if not seller.l10n_it_tax_system:
            raise UserError(_("The seller's company must have a tax system."))

        # <1.2.2>
        if not seller.street and not seller.street2:
            raise UserError(
                _("%s must have a street.") % (seller.display_name))
        if not seller.zip:
            raise UserError(
                _("%s must have a post code.") % (seller.display_name))
        if len(seller.zip) != 5 and seller.country_id.code == 'IT':
            raise UserError(
                _("%s must have a post code of length 5.") %
                (seller.display_name))
        if not seller.city:
            raise UserError(_("%s must have a city.") % (seller.display_name))
        if not seller.country_id:
            raise UserError(
                _("%s must have a country.") % (seller.display_name))

        # <1.4.1>
        if not buyer.vat and not buyer.l10n_it_codice_fiscale and buyer.country_id.code == 'IT':
            raise UserError(
                _("The buyer, %s, or his company must have either a VAT number either a tax code (Codice Fiscale)."
                  ) % (buyer.display_name))

        # <1.4.2>
        if not buyer.street and not buyer.street2:
            raise UserError(_("%s must have a street.") % (buyer.display_name))
        if not buyer.zip:
            raise UserError(
                _("%s must have a post code.") % (buyer.display_name))
        if len(buyer.zip) != 5 and buyer.country_id.code == 'IT':
            raise UserError(
                _("%s must have a post code of length 5.") %
                (buyer.display_name))
        if not buyer.city:
            raise UserError(_("%s must have a city.") % (buyer.display_name))
        if not buyer.country_id:
            raise UserError(
                _("%s must have a country.") % (buyer.display_name))

        # <2.2.1>
        for invoice_line in self.invoice_line_ids:
            if len(invoice_line.tax_ids) != 1:
                raise UserError(
                    _("You must select one and only one tax by line."))

        for tax_line in self.line_ids.filtered(lambda line: line.tax_line_id):
            if not tax_line.tax_line_id.l10n_it_has_exoneration and tax_line.tax_line_id.amount == 0:
                raise ValidationError(
                    _("%s has an amount of 0.0, you must indicate the kind of exoneration."
                      % tax_line.name))
Пример #12
0
    def default_get(self, fields):
        rec = super(account_abstract_payment, self).default_get(fields)
        active_ids = self._context.get('active_ids')
        active_model = self._context.get('active_model')

        # Check for selected invoices ids
        if not active_ids or active_model != 'account.invoice':
            return rec

        invoices = self.env['account.invoice'].browse(active_ids)

        # Check all invoices are open
        if any(invoice.state != 'open' for invoice in invoices):
            raise UserError(
                _("You can only register payments for open invoices"))
        # Check all invoices have the same currency
        if any(inv.currency_id != invoices[0].currency_id for inv in invoices):
            raise UserError(
                _("In order to pay multiple invoices at once, they must use the same currency."
                  ))
        # Check if, in batch payments, there are not negative invoices and positive invoices
        dtype = invoices[0].type
        for inv in invoices[1:]:
            if inv.type != dtype:
                if ((dtype == 'in_refund' and inv.type == 'in_invoice') or
                    (dtype == 'in_invoice' and inv.type == 'in_refund')):
                    raise UserError(
                        _("You cannot register payments for vendor bills and supplier refunds at the same time."
                          ))
                if ((dtype == 'out_refund' and inv.type == 'out_invoice') or
                    (dtype == 'out_invoice' and inv.type == 'out_refund')):
                    raise UserError(
                        _("You cannot register payments for customer invoices and credit notes at the same time."
                          ))

        # Look if we are mixin multiple commercial_partner or customer invoices with vendor bills
        multi = any(
            inv.commercial_partner_id != invoices[0].commercial_partner_id
            or MAP_INVOICE_TYPE_PARTNER_TYPE[
                inv.type] != MAP_INVOICE_TYPE_PARTNER_TYPE[invoices[0].type]
            or inv.account_id != invoices[0].account_id
            or inv.partner_bank_id != invoices[0].partner_bank_id
            for inv in invoices)

        currency = invoices[0].currency_id

        total_amount = self._compute_payment_amount(invoices=invoices,
                                                    currency=currency)

        rec.update({
            'amount':
            abs(total_amount),
            'currency_id':
            currency.id,
            'payment_type':
            total_amount > 0 and 'inbound' or 'outbound',
            'partner_id':
            False if multi else invoices[0].commercial_partner_id.id,
            'partner_type':
            False
            if multi else MAP_INVOICE_TYPE_PARTNER_TYPE[invoices[0].type],
            'communication':
            ' '.join([ref for ref in invoices.mapped('reference')
                      if ref])[:2000],
            'invoice_ids': [(6, 0, invoices.ids)],
            'multi':
            multi,
        })
        return rec
Пример #13
0
    def _run_buy(self, procurements):
        procurements_by_po_domain = defaultdict(list)
        for procurement, rule in procurements:

            # Get the schedule date in order to find a valid seller
            procurement_date_planned = fields.Datetime.from_string(
                procurement.values['date_planned'])
            schedule_date = (
                procurement_date_planned -
                relativedelta(days=procurement.company_id.po_lead))

            supplier = procurement.product_id._select_seller(
                quantity=procurement.product_qty,
                date=schedule_date.date(),
                uom_id=procurement.product_uom)

            if not supplier:
                msg = _(
                    'There is no matching vendor price to generate the purchase order for product %s (no vendor defined, minimum quantity not reached, dates not valid, ...). Go on the product form and complete the list of vendors.'
                ) % (procurement.product_id.display_name)
                raise UserError(msg)

            partner = supplier.name
            # we put `supplier_info` in values for extensibility purposes
            procurement.values['supplier'] = supplier
            procurement.values['propagate_date'] = rule.propagate_date
            procurement.values[
                'propagate_date_minimum_delta'] = rule.propagate_date_minimum_delta
            procurement.values['propagate_cancel'] = rule.propagate_cancel

            domain = rule._make_po_get_domain(procurement.company_id,
                                              procurement.values, partner)
            procurements_by_po_domain[domain].append((procurement, rule))

        for domain, procurements_rules in procurements_by_po_domain.items():
            # Get the procurements for the current domain.
            # Get the rules for the current domain. Their only use is to create
            # the PO if it does not exist.
            procurements, rules = zip(*procurements_rules)

            # Get the set of procurement origin for the current domain.
            origins = set([p.origin for p in procurements])
            # Check if a PO exists for the current domain.
            po = self.env['purchase.order'].sudo().search(
                [dom for dom in domain], limit=1)
            company_id = procurements[0].company_id
            if not po:
                # We need a rule to generate the PO. However the rule generated
                # the same domain for PO and the _prepare_purchase_order method
                # should only uses the common rules's fields.
                vals = rules[0]._prepare_purchase_order(
                    company_id, origins, [p.values for p in procurements])
                # The company_id is the same for all procurements since
                # _make_po_get_domain add the company in the domain.
                po = self.env['purchase.order'].with_context(
                    force_company=company_id.id).sudo().create(vals)
            else:
                # If a purchase order is found, adapt its `origin` field.
                if po.origin:
                    missing_origins = origins - set(po.origin.split(', '))
                    if missing_origins:
                        po.write({
                            'origin':
                            po.origin + ', ' + ', '.join(missing_origins)
                        })
                else:
                    po.write({'origin': ', '.join(origins)})

            procurements_to_merge = self._get_procurements_to_merge(
                procurements)
            procurements = self._merge_procurements(procurements_to_merge)

            po_lines_by_product = {}
            grouped_po_lines = groupby(po.order_line.filtered(
                lambda l: not l.display_type and l.product_uom == l.product_id.
                uom_po_id).sorted('product_id'),
                                       key=lambda l: l.product_id.id)
            for product, po_lines in grouped_po_lines:
                po_lines_by_product[product] = self.env[
                    'purchase.order.line'].concat(*list(po_lines))
            po_line_values = []
            for procurement in procurements:
                po_lines = po_lines_by_product.get(
                    procurement.product_id.id, self.env['purchase.order.line'])
                po_line = po_lines._find_candidate(*procurement)

                if po_line:
                    # If the procurement can be merge in an existing line. Directly
                    # write the new values on it.
                    vals = self._update_purchase_order_line(
                        procurement.product_id, procurement.product_qty,
                        procurement.product_uom, company_id,
                        procurement.values, po_line)
                    po_line.write(vals)
                else:
                    # If it does not exist a PO line for current procurement.
                    # Generate the create values for it and add it to a list in
                    # order to create it in batch.
                    partner = procurement.values['supplier'].name
                    po_line_values.append(
                        self._prepare_purchase_order_line(
                            procurement.product_id, procurement.product_qty,
                            procurement.product_uom, procurement.company_id,
                            procurement.values, po))
            self.env['purchase.order.line'].sudo().create(po_line_values)
Пример #14
0
 def button_cancel(self):
     if any('done' in event.mapped('registration_ids.state') for event in self):
         raise UserError(_("There are already attendees who attended this event. Please reset it to draft if you want to cancel this event."))
     self.registration_ids.write({'state': 'cancel'})
     self.state = 'cancel'
Пример #15
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
Пример #16
0
    def _query_get_get(self, obj='l'):

        fiscalyear_obj = self.env['account.fiscalyear']
        fiscalperiod_obj = self.env['account.period']
        account_obj = self.env['account.account']
        fiscalyear_ids = []
        context = dict(self._context or {})
        initial_bal = context.get('initial_bal', False)
        company_clause = " "
        query = ''
        query_params = {}
        if context.get('company_id'):
            company_clause = " AND " + obj + ".company_id = %(company_id)s"
            query_params['company_id'] = context['company_id']
        if not context.get('fiscalyear'):
            if context.get('all_fiscalyear'):
                #this option is needed by the aged balance report because otherwise, if we search only the draft ones, an open invoice of a closed fiscalyear won't be displayed
                fiscalyear_ids = fiscalyear_obj.search([])
            else:
                fiscalyear_ids = self.env['account.fiscalyear'].search([
                    ('state', '=', 'draft')
                ]).ids
        else:
            #for initial balance as well as for normal query, we check only the selected FY because the best practice is to generate the FY opening entries
            fiscalyear_ids = context['fiscalyear']
            if isinstance(context['fiscalyear'], (int, long)):
                fiscalyear_ids = [fiscalyear_ids]

        query_params['fiscalyear_ids'] = tuple(fiscalyear_ids) or (0, )
        state = context.get('state', False)
        where_move_state = ''
        where_move_lines_by_date = ''

        if context.get('date_from') and context.get('date_to'):
            query_params['date_from'] = context['date_from']
            query_params['date_to'] = context['date_to']
            if initial_bal:
                where_move_lines_by_date = " AND " + obj + ".move_id IN (SELECT id FROM account_move WHERE date < %(date_from)s)"
            else:
                where_move_lines_by_date = " AND " + obj + ".move_id IN (SELECT id FROM account_move WHERE date >= %(date_from)s AND date <= %(date_to)s)"

        if state:
            if state.lower() not in ['all']:
                query_params['state'] = state
                where_move_state = " AND " + obj + ".move_id IN (SELECT id FROM account_move WHERE account_move.state = %(state)s)"
        if context.get('period_from') and context.get(
                'period_to') and not context.get('periods'):
            if initial_bal:
                period_company_id = fiscalperiod_obj.browse(
                    context['period_from'], context=context).company_id.id
                first_period = fiscalperiod_obj.search(
                    [('company_id', '=', period_company_id)],
                    order='date_start',
                    limit=1)[0]
                context['periods'] = fiscalperiod_obj.build_ctx_periods(
                    first_period, context['period_from'])
            else:
                context['periods'] = fiscalperiod_obj.build_ctx_periods(
                    context['period_from'], context['period_to'])
        if 'periods_special' in context:
            periods_special = ' AND special = %s ' % bool(
                context.get('periods_special'))
        else:
            periods_special = ''
        if context.get('periods'):
            query_params['period_ids'] = tuple(context['periods'])
            if initial_bal:
                query = obj + ".state <> 'draft' AND " + obj + ".period_id IN (SELECT id FROM account_period WHERE fiscalyear_id IN %(fiscalyear_ids)s" + periods_special + ")" + where_move_state + where_move_lines_by_date
                period_ids = fiscalperiod_obj.search(
                    [('id', 'in', context['periods'])],
                    order='date_start',
                    limit=1)
                if period_ids and period_ids[0]:
                    first_period = fiscalperiod_obj.browse(period_ids[0],
                                                           context=context)
                    query_params['date_start'] = first_period.date_start
                    query = obj + ".state <> 'draft' AND " + obj + ".period_id IN (SELECT id FROM account_period WHERE fiscalyear_id IN %(fiscalyear_ids)s AND date_start <= %(date_start)s AND id NOT IN %(period_ids)s" + periods_special + ")" + where_move_state + where_move_lines_by_date
            else:
                query = obj + ".state <> 'draft' AND " + obj + ".period_id IN (SELECT id FROM account_period WHERE fiscalyear_id IN %(fiscalyear_ids)s AND id IN %(period_ids)s" + periods_special + ")" + where_move_state + where_move_lines_by_date
        else:
            query = obj + ".state <> 'draft' AND " + obj + ".period_id IN (SELECT id FROM account_period WHERE fiscalyear_id IN %(fiscalyear_ids)s" + periods_special + ")" + where_move_state + where_move_lines_by_date

        if initial_bal and not context.get(
                'periods') and not where_move_lines_by_date:
            #we didn't pass any filter in the context, and the initial balance can't be computed using only the fiscalyear otherwise entries will be summed twice
            #so we have to invalidate this query
            raise UserError(
                _('Warning!'),
                _("You have not supplied enough arguments to compute the initial balance, please select a period and a journal in the context."
                  ))

        if context.get('journal_ids'):
            query_params['journal_ids'] = tuple(context['journal_ids'])
            query += ' AND ' + obj + '.journal_id IN %(journal_ids)s'

        if context.get('chart_account_id'):
            child_ids = account_obj._get_children_and_consol(
                [context['chart_account_id']], context=context)
            query_params['child_ids'] = tuple(child_ids)
            query += ' AND ' + obj + '.account_id IN %(child_ids)s'

        query += company_clause
        cursor = self.env.cr
        return cursor.mogrify(query, query_params)
Пример #17
0
 def unlink(self):
     if self.filtered(lambda uom: uom.measure_type == 'time'):
         raise UserError(
             _("You cannot delete this UoM as it is used by the system. You should rather archive it."
               ))
     return super(UoM, self).unlink()
Пример #18
0
    def create(self, values):
        config_id = values.get('config_id') or self.env.context.get(
            'default_config_id')
        if not config_id:
            raise UserError(
                _("You should assign a Point of Sale to your session."))

        # journal_id is not required on the pos_config because it does not
        # exists at the installation. If nothing is configured at the
        # installation we do the minimal configuration. Impossible to do in
        # the .xml files as the CoA is not yet installed.
        pos_config = self.env['pos.config'].browse(config_id)
        ctx = dict(self.env.context, company_id=pos_config.company_id.id)
        if not pos_config.journal_id:
            default_journals = pos_config.with_context(ctx).default_get(
                ['journal_id', 'invoice_journal_id'])
            if (not default_journals.get('journal_id')
                    or not default_journals.get('invoice_journal_id')):
                raise UserError(
                    _("Unable to open the session. You have to assign a sales journal to your point of sale."
                      ))
            pos_config.with_context(ctx).sudo().write({
                'journal_id':
                default_journals['journal_id'],
                'invoice_journal_id':
                default_journals['invoice_journal_id']
            })
        # define some cash journal if no payment method exists
        if not pos_config.journal_ids:
            Journal = self.env['account.journal']
            journals = Journal.with_context(ctx).search([
                ('journal_user', '=', True), ('type', '=', 'cash')
            ])
            if not journals:
                journals = Journal.with_context(ctx).search([('type', '=',
                                                              'cash')])
                if not journals:
                    journals = Journal.with_context(ctx).search([
                        ('journal_user', '=', True)
                    ])
            if not journals:
                raise ValidationError(
                    _("No payment method configured! \nEither no Chart of Account is installed or no payment method is configured for this POS."
                      ))
            journals.sudo().write({'journal_user': True})
            pos_config.sudo().write({'journal_ids': [(6, 0, journals.ids)]})

        pos_name = self.env['ir.sequence'].with_context(ctx).next_by_code(
            'pos.session')
        if values.get('name'):
            pos_name += ' ' + values['name']

        statements = []
        ABS = self.env['account.bank.statement']
        uid = SUPERUSER_ID if self.env.user.has_group(
            'point_of_sale.group_pos_user') else self.env.user.id
        for journal in pos_config.journal_ids:
            # set the journal_id which should be used by
            # account.bank.statement to set the opening balance of the
            # newly created bank statement
            ctx['journal_id'] = journal.id if pos_config.cash_control and journal.type == 'cash' else False
            st_values = {
                'journal_id':
                journal.id,
                'user_id':
                self.env.user.id,
                'name':
                pos_name,
                'balance_start':
                self.env["account.bank.statement"]._get_opening_balance(
                    journal.id) if journal.type == 'cash' else 0
            }

            statements.append(
                ABS.with_context(ctx).sudo(uid).create(st_values).id)

        values.update({
            'name': pos_name,
            'statement_ids': [(6, 0, statements)],
            'config_id': config_id
        })

        res = super(PosSession,
                    self.with_context(ctx).sudo(uid)).create(values)
        if not pos_config.cash_control:
            res.action_pos_session_open()

        return res
Пример #19
0
 def unlink(self):
     if 'done' in self.mapped('state'):
         raise UserError(_('You cannot delete a scrap which is done.'))
     return super(StockScrap, self).unlink()
    def action_forward(self):
        self.ensure_one()
        template = self.env.ref(
            'website_crm_partner_assign.email_template_lead_forward_mail',
            False)
        if not template:
            raise UserError(
                _('The Forward Email Template is not in the database'))
        portal_group = self.env.ref('base.group_portal')

        local_context = self.env.context.copy()
        if not (self.forward_type == 'single'):
            no_email = set()
            for lead in self.assignation_lines:
                if lead.partner_assigned_id and not lead.partner_assigned_id.email:
                    no_email.add(lead.partner_assigned_id.name)
            if no_email:
                raise UserError(
                    _('Set an email address for the partner(s): %s') %
                    ", ".join(no_email))
        if self.forward_type == 'single' and not self.partner_id.email:
            raise UserError(
                _('Set an email address for the partner %s') %
                self.partner_id.name)

        partners_leads = {}
        for lead in self.assignation_lines:
            partner = self.forward_type == 'single' and self.partner_id or lead.partner_assigned_id
            lead_details = {
                'lead_link': lead.lead_link,
                'lead_id': lead.lead_id,
            }
            if partner:
                partner_leads = partners_leads.get(partner.id)
                if partner_leads:
                    partner_leads['leads'].append(lead_details)
                else:
                    partners_leads[partner.id] = {
                        'partner': partner,
                        'leads': [lead_details]
                    }

        for partner_id, partner_leads in partners_leads.items():
            in_portal = False
            if portal_group:
                for contact in (
                        partner.child_ids
                        or partner).filtered(lambda contact: contact.user_ids):
                    in_portal = portal_group.id in [
                        g.id for g in contact.user_ids[0].groups_id
                    ]

            local_context['partner_id'] = partner_leads['partner']
            local_context['partner_leads'] = partner_leads['leads']
            local_context['partner_in_portal'] = in_portal
            template.with_context(local_context).send_mail(self.id)
            leads = self.env['crm.lead']
            for lead_data in partner_leads['leads']:
                leads |= lead_data['lead_id']
            values = {
                'partner_assigned_id': partner_id,
                'user_id': partner_leads['partner'].user_id.id
            }
            leads.with_context(mail_auto_subscribe_no_notify=1).write(values)
            self.env['crm.lead'].message_subscribe([partner_id])
        return True
Пример #21
0
    def _render_template(self,
                         template_txt,
                         model,
                         res_ids,
                         post_process=False):
        """ Render the given template text, replace mako expressions ``${expr}``
        with the result of evaluating these expressions with an evaluation
        context containing:

         - ``user``: Model of the current user
         - ``object``: record of the document record this mail is related to
         - ``context``: the context passed to the mail composition wizard

        :param str template_txt: the template text to render
        :param str model: model name of the document record this mail is related to.
        :param int res_ids: list of ids of document records those mails are related to.
        """
        multi_mode = True
        if isinstance(res_ids, int):
            multi_mode = False
            res_ids = [res_ids]

        results = dict.fromkeys(res_ids, u"")

        # try to load the template
        try:
            mako_env = mako_safe_template_env if self.env.context.get(
                'safe') else mako_template_env
            template = mako_env.from_string(tools.ustr(template_txt))
        except Exception:
            _logger.info("Failed to load template %r",
                         template_txt,
                         exc_info=True)
            return multi_mode and results or results[res_ids[0]]

        # prepare template variables
        records = self.env[model].browse(
            it for it in res_ids if it)  # filter to avoid browsing [None]
        res_to_rec = dict.fromkeys(res_ids, None)
        for record in records:
            res_to_rec[record.id] = record
        variables = {
            'format_date':
            lambda date, date_format=False, lang_code=False: format_date(
                self.env, date, date_format, lang_code),
            'format_datetime':
            lambda dt, tz=False, dt_format=False, lang_code=False:
            format_datetime(self.env, dt, tz, dt_format, lang_code),
            'format_amount':
            lambda amount, currency, lang_code=False: tools.format_amount(
                self.env, amount, currency, lang_code),
            'format_duration':
            lambda value: tools.format_duration(value),
            'user':
            self.env.user,
            'ctx':
            self._context,  # context kw would clash with mako internals
        }
        for res_id, record in res_to_rec.items():
            variables['object'] = record
            try:
                render_result = template.render(variables)
            except Exception:
                _logger.info("Failed to render template %r using values %r" %
                             (template, variables),
                             exc_info=True)
                raise UserError(
                    _("Failed to render template %r using values %r") %
                    (template, variables))
            if render_result == u"False":
                render_result = u""
            results[res_id] = render_result

        if post_process:
            for res_id, result in results.items():
                results[res_id] = self.render_post_process(result)

        return multi_mode and results or results[res_ids[0]]
Пример #22
0
 def _assert_phone_field(self):
     if not hasattr(self, "_phone_get_number_fields"):
         raise UserError(_('Invalid primary phone field on model %s') % self._name)
     if not any(fname in self and self._fields[fname].type == 'char' for fname in self._phone_get_number_fields()):
         raise UserError(_('Invalid primary phone field on model %s') % self._name)
Пример #23
0
 def unlink(self):
     raise UserError(
         _('Sale Closings are not meant to be written or deleted under any circumstances.'
           ))
Пример #24
0
    def get_invoice(self):
        """ Create invoice for fee payment process of student """
        inv_obj = self.env['account.move']
        partner_id = self.student_id.partner_id
        student = self.student_id
        account_id = False
        product = self.product_id

        if product.property_account_income_id:
            account_id = product.property_account_income_id.id
        if not account_id:
            account_id = product.categ_id.property_account_income_categ_id.id
        if not account_id:
            raise UserError(
                _('There is no income account defined for this product: "%s".'
                  'You may have to install a chart of account from Accounting'
                  ' app, settings menu.') % product.name)
        if self.amount <= 0.00:
            raise UserError(
                _('The value of the deposit amount must be positive.'))
        else:
            amount = self.amount
            name = product.name

        invoice = inv_obj.create({
            'partner_id': student.name,
            'type': 'out_invoice',
            'partner_id': partner_id.id,
        })
        element_id = self.env['op.fees.element'].search([
            ('fees_terms_line_id', '=', self.fees_line_id.id)
        ])
        for records in element_id:

            if records:
                line_values = {
                    'name': records.product_id.name,
                    'account_id': account_id,
                    'price_unit': records.value * self.amount / 100,
                    'quantity': 1.0,
                    'discount': 0.0,
                    'product_uom_id': records.product_id.uom_id.id,
                    'product_id': records.product_id.id,
                }
                invoice.write({'invoice_line_ids': [(0, 0, line_values)]})

        if not element_id:
            line_values = {
                'partner_id': name,
                # 'origin': student.gr_no,
                'account_id': account_id,
                'price_unit': amount,
                'quantity': 1.0,
                'discount': 0.0,
                'product_uom_id': product.uom_id.id,
                'product_id': product.id
            }
            invoice.write({'invoice_line_ids': [(0, 0, line_values)]})

        invoice._compute_invoice_taxes_by_group()
        self.state = 'invoice'
        self.invoice_id = invoice.id
        return True
Пример #25
0
 def create(self,vals_list):
     res = super(slider, self).create(vals_list)
     if res.slider_type=='product' and not res.slider_filter_ids:
         raise UserError(_('Sorry! Please set product filters first'))
     else:
         return res
Пример #26
0
    def attachment_add(self,
                       name,
                       file,
                       res_model,
                       res_id,
                       access_token=None,
                       **kwargs):
        """Process a file uploaded from the portal chatter and create the
        corresponding `ir.attachment`.

        The attachment will be created "pending" until the associated message
        is actually created, and it will be garbage collected otherwise.

        :param name: name of the file to save.
        :type name: string

        :param file: the file to save
        :type file: werkzeug.FileStorage

        :param res_model: name of the model of the original document.
            To check access rights only, it will not be saved here.
        :type res_model: string

        :param res_id: id of the original document.
            To check access rights only, it will not be saved here.
        :type res_id: int

        :param access_token: access_token of the original document.
            To check access rights only, it will not be saved here.
        :type access_token: string

        :return: attachment data {id, name, mimetype, file_size, access_token}
        :rtype: dict
        """
        try:
            self._document_check_access(res_model,
                                        int(res_id),
                                        access_token=access_token)
        except (AccessError, MissingError) as e:
            raise UserError(
                _("The document does not exist or you do not have the rights to access it."
                  ))

        IrAttachment = request.env['ir.attachment']
        access_token = False

        # Avoid using sudo or creating access_token when not necessary: internal
        # users can create attachments, as opposed to public and portal users.
        if not request.env.user.has_group('base.group_user'):
            IrAttachment = IrAttachment.sudo().with_context(
                binary_field_real_user=IrAttachment.env.user)
            access_token = IrAttachment._generate_access_token()

        # At this point the related message does not exist yet, so we assign
        # those specific res_model and res_is. They will be correctly set
        # when the message is created: see `portal_chatter_post`,
        # or garbage collected otherwise: see  `_garbage_collect_attachments`.
        attachment = IrAttachment.create({
            'name': name,
            'datas': base64.b64encode(file.read()),
            'res_model': 'mail.compose.message',
            'res_id': 0,
            'access_token': access_token,
        })
        return request.make_response(data=json.dumps(
            attachment.read(
                ['id', 'name', 'mimetype', 'file_size', 'access_token'])[0]),
                                     headers=[('Content-Type',
                                               'application/json')])
Пример #27
0
    def action_unbuild(self):
        self.ensure_one()
        self._check_company()
        if self.product_id.tracking != 'none' and not self.lot_id.id:
            raise UserError(_('You should provide a lot number for the final product.'))

        if self.mo_id:
            if self.mo_id.state != 'done':
                raise UserError(_('You cannot unbuild a undone manufacturing order.'))

        consume_moves = self._generate_consume_moves()
        consume_moves._action_confirm()
        produce_moves = self._generate_produce_moves()

        finished_move = consume_moves.filtered(lambda m: m.product_id == self.product_id)
        consume_moves -= finished_move

        if any(produce_move.has_tracking != 'none' and not self.mo_id for produce_move in produce_moves):
            raise UserError(_('Some of your components are tracked, you have to specify a manufacturing order in order to retrieve the correct components.'))

        if any(consume_move.has_tracking != 'none' and not self.mo_id for consume_move in consume_moves):
            raise UserError(_('Some of your byproducts are tracked, you have to specify a manufacturing order in order to retrieve the correct byproducts.'))

        if finished_move.has_tracking != 'none':
            self.env['stock.move.line'].create({
                'move_id': finished_move.id,
                'lot_id': self.lot_id.id,
                'qty_done': finished_move.product_uom_qty,
                'product_id': finished_move.product_id.id,
                'product_uom_id': finished_move.product_uom.id,
                'location_id': finished_move.location_id.id,
                'location_dest_id': finished_move.location_dest_id.id,
            })
        else:
            finished_move.quantity_done = finished_move.product_uom_qty

        # TODO: Will fail if user do more than one unbuild with lot on the same MO. Need to check what other unbuild has aready took
        for move in produce_moves | consume_moves:
            if move.has_tracking != 'none':
                original_move = move in produce_moves and self.mo_id.move_raw_ids or self.mo_id.move_finished_ids
                original_move = original_move.filtered(lambda m: m.product_id == move.product_id)
                needed_quantity = move.product_qty
                moves_lines = original_move.mapped('move_line_ids')
                if move in produce_moves and self.lot_id:
                    moves_lines = moves_lines.filtered(lambda ml: self.lot_id in ml.lot_produced_ids)
                for move_line in moves_lines:
                    # Iterate over all move_lines until we unbuilded the correct quantity.
                    taken_quantity = min(needed_quantity, move_line.qty_done)
                    if taken_quantity:
                        self.env['stock.move.line'].create({
                            'move_id': move.id,
                            'lot_id': move_line.lot_id.id,
                            'qty_done': taken_quantity,
                            'product_id': move.product_id.id,
                            'product_uom_id': move_line.product_uom_id.id,
                            'location_id': move.location_id.id,
                            'location_dest_id': move.location_dest_id.id,
                        })
                        needed_quantity -= taken_quantity
            else:
                move.quantity_done = move.product_uom_qty

        finished_move._action_done()
        consume_moves._action_done()
        produce_moves._action_done()
        produced_move_line_ids = produce_moves.mapped('move_line_ids').filtered(lambda ml: ml.qty_done > 0)
        consume_moves.mapped('move_line_ids').write({'produce_line_ids': [(6, 0, produced_move_line_ids.ids)]})

        return self.write({'state': 'done'})
Пример #28
0
 def name_create(self, name):
     # prevent to use string as product_tmpl_id
     if isinstance(name, str):
         raise UserError(
             _("You cannot create a new Bill of Material from here."))
     return super(MrpBom, self).name_create(name)
Пример #29
0
 def unlink(self):
     for line in self:
         if line.order_id.state in ['purchase', 'done']:
             raise UserError(_('Cannot delete a purchase order line which is in state \'%s\'.') % (line.state,))
     return super(PurchaseOrderLine, self).unlink()
Пример #30
0
    def _cart_update(self,
                     product_id=None,
                     line_id=None,
                     add_qty=0,
                     set_qty=0,
                     **kwargs):
        """ Add or set product quantity, add_qty can be negative """
        self.ensure_one()
        product_context = dict(self.env.context)
        product_context.setdefault('lang', self.sudo().partner_id.lang)
        SaleOrderLineSudo = self.env['sale.order.line'].sudo().with_context(
            product_context)

        try:
            if add_qty:
                add_qty = float(add_qty)
        except ValueError:
            add_qty = 1
        try:
            if set_qty:
                set_qty = float(set_qty)
        except ValueError:
            set_qty = 0
        quantity = 0
        order_line = False
        if self.state != 'draft':
            request.session['sale_order_id'] = None
            raise UserError(
                _('It is forbidden to modify a sales order which is not in draft status.'
                  ))
        if line_id is not False:
            order_line = self._cart_find_product_line(product_id, line_id,
                                                      **kwargs)[:1]

        # Create line if no line with product_id can be located
        if not order_line:
            # change lang to get correct name of attributes/values
            product = self.env['product.product'].with_context(
                product_context).browse(int(product_id))

            if not product:
                raise UserError(
                    _("The given product does not exist therefore it cannot be added to cart."
                      ))

            no_variant_attribute_values = kwargs.get(
                'no_variant_attribute_values') or []
            received_no_variant_values = product.env[
                'product.template.attribute.value'].browse([
                    int(ptav['value']) for ptav in no_variant_attribute_values
                ])
            received_combination = product.product_template_attribute_value_ids | received_no_variant_values
            product_template = product.product_tmpl_id

            # handle all cases where incorrect or incomplete data are received
            combination = product_template._get_closest_possible_combination(
                received_combination)

            # get or create (if dynamic) the correct variant
            product = product_template._create_product_variant(combination)

            if not product:
                raise UserError(
                    _("The given combination does not exist therefore it cannot be added to cart."
                      ))

            product_id = product.id

            values = self._website_product_id_change(self.id,
                                                     product_id,
                                                     qty=1)

            # add no_variant attributes that were not received
            for ptav in combination.filtered(
                    lambda ptav: ptav.attribute_id.create_variant ==
                    'no_variant' and ptav not in received_no_variant_values):
                no_variant_attribute_values.append({
                    'value':
                    ptav.id,
                    'attribute_name':
                    ptav.attribute_id.name,
                    'attribute_value_name':
                    ptav.name,
                })

            # save no_variant attributes values
            if no_variant_attribute_values:
                values['product_no_variant_attribute_value_ids'] = [(6, 0, [
                    int(attribute['value'])
                    for attribute in no_variant_attribute_values
                ])]

            # add is_custom attribute values that were not received
            custom_values = kwargs.get('product_custom_attribute_values') or []
            received_custom_values = product.env[
                'product.attribute.value'].browse([
                    int(ptav['attribute_value_id']) for ptav in custom_values
                ])

            for ptav in combination.filtered(
                    lambda ptav: ptav.is_custom and ptav.
                    product_attribute_value_id not in received_custom_values):
                custom_values.append({
                    'attribute_value_id':
                    ptav.product_attribute_value_id.id,
                    'attribute_value_name':
                    ptav.name,
                    'custom_value':
                    '',
                })

            # save is_custom attributes values
            if custom_values:
                values['product_custom_attribute_value_ids'] = [(0, 0, {
                    'attribute_value_id':
                    custom_value['attribute_value_id'],
                    'custom_value':
                    custom_value['custom_value']
                }) for custom_value in custom_values]

            # create the line
            order_line = SaleOrderLineSudo.create(values)
            # Generate the description with everything. This is done after
            # creating because the following related fields have to be set:
            # - product_no_variant_attribute_value_ids
            # - product_custom_attribute_value_ids
            order_line.name = order_line.get_sale_order_line_multiline_description_sale(
                product)

            try:
                order_line._compute_tax_id()
            except ValidationError as e:
                # The validation may occur in backend (eg: taxcloud) but should fail silently in frontend
                _logger.debug("ValidationError occurs during tax compute. %s" %
                              (e))
            if add_qty:
                add_qty -= 1

        # compute new quantity
        if set_qty:
            quantity = set_qty
        elif add_qty is not None:
            quantity = order_line.product_uom_qty + (add_qty or 0)

        # Remove zero of negative lines
        if quantity <= 0:
            order_line.unlink()
        else:
            # update line
            no_variant_attributes_price_extra = [
                ptav.price_extra
                for ptav in order_line.product_no_variant_attribute_value_ids
            ]
            values = self.with_context(
                no_variant_attributes_price_extra=
                no_variant_attributes_price_extra)._website_product_id_change(
                    self.id, product_id, qty=quantity)
            if self.pricelist_id.discount_policy == 'with_discount' and not self.env.context.get(
                    'fixed_price'):
                order = self.sudo().browse(self.id)
                product_context.update({
                    'partner': order.partner_id,
                    'quantity': quantity,
                    'date': order.date_order,
                    'pricelist': order.pricelist_id.id,
                })
                product = self.env['product.product'].with_context(
                    product_context).browse(product_id)
                values['price_unit'] = self.env[
                    'account.tax']._fix_tax_included_price_company(
                        order_line._get_display_price(product),
                        order_line.product_id.taxes_id, order_line.tax_id,
                        self.company_id)

            order_line.write(values)

            # link a product to the sales order
            if kwargs.get('linked_line_id'):
                linked_line = SaleOrderLineSudo.browse(
                    kwargs['linked_line_id'])
                order_line.write({
                    'linked_line_id':
                    linked_line.id,
                    'name':
                    order_line.name + "\n" + _("Option for:") + ' ' +
                    linked_line.product_id.display_name,
                })
                linked_line.write({
                    "name":
                    linked_line.name + "\n" + _("Option:") + ' ' +
                    order_line.product_id.display_name
                })

        option_lines = self.order_line.filtered(
            lambda l: l.linked_line_id.id == order_line.id)
        for option_line_id in option_lines:
            self._cart_update(option_line_id.product_id.id, option_line_id.id,
                              add_qty, set_qty, **kwargs)

        return {
            'line_id': order_line.id,
            'quantity': quantity,
            'option_ids': list(set(option_lines.ids))
        }