diff --git a/sale_usability/__init__.py b/sale_usability/__init__.py
index 0650744..aee8895 100644
--- a/sale_usability/__init__.py
+++ b/sale_usability/__init__.py
@@ -1 +1,2 @@
from . import models
+from . import wizards
diff --git a/sale_usability/__manifest__.py b/sale_usability/__manifest__.py
index c56a82e..e03f307 100644
--- a/sale_usability/__manifest__.py
+++ b/sale_usability/__manifest__.py
@@ -23,6 +23,8 @@
'views/account_move.xml',
'views/res_company.xml',
"views/res_partner.xml",
+ 'wizards/sale_invoice_discount_all_lines_view.xml',
+ 'security/ir.model.access.csv',
],
'installable': True,
}
diff --git a/sale_usability/security/ir.model.access.csv b/sale_usability/security/ir.model.access.csv
new file mode 100644
index 0000000..0fede3e
--- /dev/null
+++ b/sale_usability/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_sale_invoice_discount_all_lines_sale,Full access on sale.invoice.discount.all.lines to Sale user,model_sale_invoice_discount_all_lines,sales_team.group_sale_salesman,1,1,1,1
+access_sale_invoice_discount_all_lines_invoice,Full access on sale.invoice.discount.all.lines to Invoice user,model_sale_invoice_discount_all_lines,account.group_account_invoice,1,1,1,1
diff --git a/sale_usability/views/sale_order.xml b/sale_usability/views/sale_order.xml
index 3e1129b..057ea5a 100644
--- a/sale_usability/views/sale_order.xml
+++ b/sale_usability/views/sale_order.xml
@@ -32,6 +32,9 @@
+
diff --git a/sale_usability/wizards/__init__.py b/sale_usability/wizards/__init__.py
new file mode 100644
index 0000000..a51d7d4
--- /dev/null
+++ b/sale_usability/wizards/__init__.py
@@ -0,0 +1 @@
+from . import sale_invoice_discount_all_lines
diff --git a/sale_usability/wizards/sale_invoice_discount_all_lines.py b/sale_usability/wizards/sale_invoice_discount_all_lines.py
new file mode 100644
index 0000000..d3a20a9
--- /dev/null
+++ b/sale_usability/wizards/sale_invoice_discount_all_lines.py
@@ -0,0 +1,95 @@
+# Copyright 2022 Akretion France (http://www.akretion.com/)
+# @author: Alexis de Lattre
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import api, models, fields, _
+from odoo.tools import float_compare
+from odoo.exceptions import UserError
+
+
+class SaleInvoiceDiscountAllLines(models.TransientModel):
+ _name = 'sale.invoice.discount.all.lines'
+ _description = 'Apply discount on all lines of a sale order or invoice'
+
+ @api.model
+ def default_get(self, fields_list):
+ res = super().default_get(fields_list)
+ if self._context.get('active_id'):
+ if self._context.get('active_model') == 'sale.order':
+ res['sale_id'] = self._context['active_id']
+ elif self._context.get('active_model') == 'account.move':
+ res['move_id'] = self._context['active_id']
+ move = self.env['account.move'].browse(res['move_id'])
+ if move.state != 'draft':
+ raise UserError(
+ _("Invoice '%s' is not in draft state.")
+ % self.move_id.display_name)
+ else:
+ # I don't translate this because it should never happen.
+ raise UserError(
+ "This wizard can only work on a sale order or an invoice.")
+ else:
+ # I don't translate this because it should never happen.
+ raise UserError("Missing active_id in context. It should never happen.")
+ return res
+
+ sale_id = fields.Many2one(
+ 'sale.order', string='Order', readonly=True)
+ move_id = fields.Many2one(
+ 'account.move', string='Invoice', readonly=True)
+ discount = fields.Float(
+ string='Discount', digits='Discount', required=True)
+ line_type = fields.Selection([
+ ('all', 'All Lines'),
+ ('products', 'All Product Lines'),
+ ('services', 'All Service Lines'),
+ ], default='all', required=True, string='Apply on')
+
+ def run(self):
+ prec = self.env['decimal.precision'].precision_get('Discount')
+ if float_compare(self.discount, 0, precision_digits=prec) < 0:
+ raise UserError(_("Discount cannot be negative."))
+ if self.sale_id:
+ record = self.sale_id
+ line_obj = self.env['sale.order.line']
+ domain = [('order_id', '=', self.sale_id.id)]
+ elif self.move_id:
+ record = self.move_id
+ if self.move_id.state != 'draft':
+ raise UserError(_(
+ "Invoice '%s' is not in draft state.") % self.move_id.display_name)
+ line_obj = self.env['account.move.line']
+ domain = [
+ ('move_id', '=', self.move_id.id),
+ ('exclude_from_invoice_tab', '=', False),
+ ]
+ else:
+ # I don't translate this because it should never happen.
+ raise UserError(
+ "The wizard is not linked to a sale order nor an invoice. "
+ "This should never happen.")
+ domain += [('display_type', '=', False)]
+ if self.line_type == 'products':
+ domain += [
+ ('product_id', '!=', False),
+ ('product_id.type', '!=', 'service'),
+ ]
+ elif self.line_type == 'services':
+ domain += [
+ ('product_id', '!=', False),
+ ('product_id.type', '=', 'service'),
+ ]
+ lines = line_obj.search(domain)
+ if not lines:
+ raise UserError(_("There are no lines to apply the discount on."))
+ lines.with_context(check_move_validity=False).write({'discount': self.discount})
+ if self.move_id:
+ self.move_id.with_context(
+ check_move_validity=False)._recompute_dynamic_lines(
+ recompute_all_taxes=True)
+ self.move_id._check_balanced()
+ record.message_post(body=_(
+ "Applied a {discount}% discount on {line_type}.").format(
+ discount=self.discount,
+ line_type=self._fields['line_type'].convert_to_export(
+ self.line_type, self)))
diff --git a/sale_usability/wizards/sale_invoice_discount_all_lines_view.xml b/sale_usability/wizards/sale_invoice_discount_all_lines_view.xml
new file mode 100644
index 0000000..83b6408
--- /dev/null
+++ b/sale_usability/wizards/sale_invoice_discount_all_lines_view.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+ sale.invoice.discount.all.lines
+
+
+
+
+
+
+ Discount on all lines
+ sale.invoice.discount.all.lines
+ form
+ new
+
+
+ form
+
+
+
+ Discount on all lines
+ sale.invoice.discount.all.lines
+ form
+ new
+
+
+ form
+
+
+