From 43c559b88c5fe22ce1a3cb3b49da424b382b5937 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Tue, 25 Oct 2022 10:08:07 +0000 Subject: [PATCH] [IMP] sale: docstrings improvements/fixes closes odoo/odoo#104115 X-original-commit: 7018ea22fedf75c16926c19e85b84dd82c23ab1b Signed-off-by: Victor Feyens (vfe) --- addons/sale/models/sale_order.py | 91 +++++++++++++++++++-------- addons/sale/models/sale_order_line.py | 28 ++++++--- 2 files changed, 84 insertions(+), 35 deletions(-) diff --git a/addons/sale/models/sale_order.py b/addons/sale/models/sale_order.py index fa65a38df58..34d635dff16 100644 --- a/addons/sale/models/sale_order.py +++ b/addons/sale/models/sale_order.py @@ -712,12 +712,12 @@ class SaleOrder(models.Model): lang = mail_template._render_lang(self.ids)[self.id] ctx = { 'default_model': 'sale.order', - 'default_res_id': self.ids[0], + 'default_res_id': self.id, 'default_use_template': bool(mail_template), 'default_template_id': mail_template.id if mail_template else None, 'default_composition_mode': 'comment', 'mark_so_as_sent': True, - 'default_email_layout_xmlid': "mail.mail_notification_layout_with_responsible_signature", + 'default_email_layout_xmlid': 'mail.mail_notification_layout_with_responsible_signature', 'proforma': self.env.context.get('proforma', False), 'force_email': True, 'model_description': self.with_context(lang=lang).type_name, @@ -748,9 +748,17 @@ class SaleOrder(models.Model): return self._get_confirmation_template() def _get_confirmation_template(self): + """ Get the mail template sent on SO confirmation (or for confirmed SO's). + + :return: `mail.template` record or None if default template wasn't found + """ return self.env.ref('sale.mail_template_sale_confirmation', raise_if_not_found=False) def action_quotation_sent(self): + """ Mark the given draft quotation(s) as sent. + + :raise: UserError if any given SO is not in draft state. + """ if self.filtered(lambda so: so.state != 'draft'): raise UserError(_("Only draft orders can be marked as sent directly.")) @@ -760,6 +768,14 @@ class SaleOrder(models.Model): self.write({'state': 'sent'}) def action_confirm(self): + """ Confirm the given quotation(s) and set their confirmation date. + + If the corresponding setting is enabled, also locks the Sale Order. + + :return: True + :rtype: bool + :raise: UserError if trying to confirm locked or cancelled SO's + """ if self._get_forbidden_state_confirm() & set(self.mapped('state')): raise UserError(_( "It is not allowed to confirm an order in the following states: %s", @@ -788,6 +804,13 @@ class SaleOrder(models.Model): return {'done', 'cancel'} def _prepare_confirmation_values(self): + """ Prepare the sales order confirmation values. + + Note: self can contain multiple records. + + :return: Sales Order confirmation values + :rtype: dict + """ return { 'state': 'sale', 'date_order': fields.Datetime.now() @@ -834,9 +857,9 @@ class SaleOrder(models.Model): self.write({'state': 'sale'}) def action_cancel(self): - """ Cancel SO after showing the cancel wizard when needed. (cfr `_show_cancel_wizard`) + """ Cancel SO after showing the cancel wizard when needed. (cfr :meth:`_show_cancel_wizard`) - For post-cancel operations, please only override `_action_cancel`. + For post-cancel operations, please only override :meth:`_action_cancel`. note: self.ensure_one() if the wizard is shown. """ @@ -876,6 +899,11 @@ class SaleOrder(models.Model): return self.write({'state': 'cancel'}) def _show_cancel_wizard(self): + """ Decide whether the sale.order.cancel wizard should be shown to cancel specified orders. + + :return: True if there are draft order(s) in the given orders + :rtype: bool + """ if self.env.context.get('disable_cancel_warning'): return False return any(so.state != 'draft' for so in self) @@ -1026,12 +1054,15 @@ class SaleOrder(models.Model): return self.env['sale.order.line'].browse(invoiceable_line_ids + down_payment_line_ids) def _create_invoices(self, grouped=False, final=False, date=None): - """ - Create the invoice associated to the SO. - :param grouped: if True, invoices are grouped by SO id. If False, invoices are grouped by - (partner_invoice_id, currency) - :param final: if True, refunds will be generated if necessary - :returns: list of created invoices + """ Create invoice(s) for the given Sales Order(s). + + :param bool grouped: if True, invoices are grouped by SO id. + If False, invoices are grouped by keys returned by :meth:`_get_invoice_grouping_keys` + :param bool final: if True, refunds will be generated if necessary + :param date: unused parameter + :returns: created invoices + :rtype: `account.move` recordset + :raises: UserError if one of the orders has no invoiceable lines. """ if not self.env['account.move'].check_access_rights('create', False): try: @@ -1361,56 +1392,64 @@ class SaleOrder(models.Model): note=_("Upsell %(order)s for customer %(customer)s", order=order_ref, customer=customer_ref)) def _prepare_analytic_account_data(self, prefix=None): - """ - Prepare method for analytic account data + """ Prepare SO analytic account creation values. - :param prefix: The prefix of the to-be-created analytic account name - :type prefix: string - :return: dictionary of value for new analytic account creation + :param str prefix: The prefix of the to-be-created analytic account name + :return: `account.analytic.account` creation values + :rtype: dict """ + self.ensure_one() name = self.name if prefix: name = prefix + ": " + self.name - plan = self.env['account.analytic.plan'].search(['|', ('company_id', '=', self.company_id.id), ('company_id', '=', False)], limit=1) + plan = self.env['account.analytic.plan'].search([ + '|', ('company_id', '=', self.company_id.id), ('company_id', '=', False) + ], limit=1) if not plan: plan = self.env['account.analytic.plan'].create({ 'name': 'Default', - 'company_id': self.company_id.id + 'company_id': self.company_id.id, }) return { 'name': name, 'code': self.client_order_ref, 'company_id': self.company_id.id, 'plan_id': plan.id, - 'partner_id': self.partner_id.id + 'partner_id': self.partner_id.id, } def _create_analytic_account(self, prefix=None): + """ Create a new analytic account for the given orders. + + :param str prefix: if specified, the account name will be ': '. + If not, the account name will be the Sales Order reference. + :return: None + """ for order in self: analytic = self.env['account.analytic.account'].create(order._prepare_analytic_account_data(prefix)) order.analytic_account_id = analytic - @api.model def _prepare_down_payment_section_line(self, **optional_values): - """ - Prepare the dict of values to create a new down payment section for a sales order line. + """ Prepare the values to create a new down payment section. - :param optional_values: any parameter that should be added to the returned down payment section + :param dict optional_values: any parameter that should be added to the returned down payment section + :return: `account.move.line` creation values + :rtype: dict """ + self.ensure_one() context = {'lang': self.partner_id.lang} down_payments_section_line = { 'display_type': 'line_section', - 'name': _('Down Payments'), + 'name': _("Down Payments"), 'product_id': False, 'product_uom_id': False, 'quantity': 0, 'discount': 0, 'price_unit': 0, - 'account_id': False + 'account_id': False, + **optional_values } del context - if optional_values: - down_payments_section_line.update(optional_values) return down_payments_section_line #=== HOOKS ===# diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py index 4005ef2dd20..ab682aed5b2 100644 --- a/addons/sale/models/sale_order_line.py +++ b/addons/sale/models/sale_order_line.py @@ -986,6 +986,11 @@ class SaleOrderLine(models.Model): return super().write(values) def _get_protected_fields(self): + """ Give the fields that should not be modified on a locked SO. + + :returns: list of field names + :rtype: list + """ return [ 'product_id', 'name', 'price_unit', 'product_uom', 'product_uom_qty', 'tax_id', 'analytic_distribution' @@ -1010,21 +1015,26 @@ class SaleOrderLine(models.Model): order.message_post(body=msg) def _check_line_unlink(self): - """ - Check whether a line can be deleted or not. + """ Check whether given lines can be deleted or not. - Lines cannot be deleted if the order is confirmed; downpayment - lines who have not yet been invoiced bypass that exception. - Also, allow deleting UX lines (notes/sections). - :rtype: recordset sale.order.line - :returns: set of lines that cannot be deleted + * Lines cannot be deleted if the order is confirmed. + * Down payment lines who have not yet been invoiced bypass that exception. + * Sections and Notes can always be deleted. + + :returns: Sales Order Lines that cannot be deleted + :rtype: `sale.order.line` recordset """ - return self.filtered(lambda line: line.state in ('sale', 'done') and (line.invoice_lines or not line.is_downpayment) and not line.display_type) + return self.filtered( + lambda line: + line.state in ('sale', 'done') + and (line.invoice_lines or not line.is_downpayment) + and not line.display_type + ) @api.ondelete(at_uninstall=False) def _unlink_except_confirmed(self): if self._check_line_unlink(): - raise UserError(_('You can not remove an order line once the sales order is confirmed.\nYou should rather set the quantity to 0.')) + raise UserError(_("You can not remove an order line once the sales order is confirmed.\nYou should rather set the quantity to 0.")) #=== BUSINESS METHODS ===#