diff --git a/addons/sale/models/account_move.py b/addons/sale/models/account_move.py
index ad38e69cd33..8c0bcb70fd7 100644
--- a/addons/sale/models/account_move.py
+++ b/addons/sale/models/account_move.py
@@ -61,8 +61,12 @@ class AccountMove(models.Model):
def action_post(self):
#inherit of the function from account.move to validate a new tax and the priceunit of a downpayment
res = super(AccountMove, self).action_post()
- line_ids = self.mapped('line_ids').filtered(lambda line: line.sale_line_ids.is_downpayment)
- for line in line_ids:
+ down_payment_lines = self.line_ids.filtered(lambda line: line.sale_line_ids.is_downpayment)
+ for line in down_payment_lines:
+
+ if not line.sale_line_ids.display_type:
+ line.sale_line_ids._compute_name()
+
try:
line.sale_line_ids.tax_id = line.tax_ids
if all(line.tax_ids.mapped('price_include')):
@@ -77,6 +81,24 @@ class AccountMove(models.Model):
pass
return res
+ def button_draft(self):
+ res = super().button_draft()
+
+ self.line_ids.filtered(
+ lambda line: line.sale_line_ids.is_downpayment and not line.sale_line_ids.display_type
+ ).sale_line_ids._compute_name()
+
+ return res
+
+ def button_cancel(self):
+ res = super().button_cancel()
+
+ self.line_ids.filtered(
+ lambda line: line.sale_line_ids.is_downpayment and not line.sale_line_ids.display_type
+ ).sale_line_ids._compute_name()
+
+ return res
+
def _post(self, soft=True):
# OVERRIDE
# Auto-reconcile the invoice with payments coming from transactions.
diff --git a/addons/sale/models/sale_order.py b/addons/sale/models/sale_order.py
index d487bf521c2..3c510182b9b 100644
--- a/addons/sale/models/sale_order.py
+++ b/addons/sale/models/sale_order.py
@@ -1245,6 +1245,25 @@ class SaleOrder(models.Model):
self.ensure_one()
return self.transaction_ids._get_last()
+ def _get_order_lines_to_report(self):
+ down_payment_lines = self.order_line.filtered(lambda line:
+ line.is_downpayment
+ and not line.display_type
+ and not line._get_downpayment_state()
+ )
+
+ def show_line(line):
+ if not line.is_downpayment:
+ return True
+ elif line.display_type and down_payment_lines:
+ return True # Only show the down payment section if down payments were posted
+ elif line in down_payment_lines:
+ return True # Only show posted down payments
+ else:
+ return False
+
+ return self.order_line.filtered(show_line)
+
# PORTAL #
def has_to_be_signed(self, include_draft=False):
diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py
index 48f7c46640e..4195cec493d 100644
--- a/addons/sale/models/sale_order_line.py
+++ b/addons/sale/models/sale_order_line.py
@@ -291,7 +291,17 @@ class SaleOrderLine(models.Model):
for line in self:
if not line.product_id:
continue
- line.name = line.with_context(lang=line.order_partner_id.lang)._get_sale_order_line_multiline_description_sale()
+
+ name = line.with_context(lang=line.order_partner_id.lang)._get_sale_order_line_multiline_description_sale()
+ if line.is_downpayment and not line.display_type:
+ context = {'lang': line.order_partner_id.lang}
+ dp_state = line._get_downpayment_state()
+ if dp_state == 'draft':
+ name = _("%(line_description)s (Draft)", line_description=name)
+ elif dp_state == 'cancel':
+ name = _("%(line_description)s (Canceled)", line_description=name)
+ del context
+ line.name = name
def _get_sale_order_line_multiline_description_sale(self):
""" Compute a default multiline description for this sales order line.
@@ -654,6 +664,20 @@ class SaleOrderLine(models.Model):
for so_line in lines_by_analytic:
so_line.qty_delivered = mapping.get(so_line.id or so_line._origin.id, 0.0)
+ def _get_downpayment_state(self):
+ self.ensure_one()
+
+ if self.display_type:
+ return ''
+
+ invoice_lines = self._get_invoice_lines()
+ if all(line.parent_state == 'draft' for line in invoice_lines):
+ return 'draft'
+ if all(line.parent_state == 'cancel' for line in invoice_lines):
+ return 'cancel'
+
+ return ''
+
def _get_delivered_quantity_by_analytic(self, additional_domain):
""" Compute and write the delivered quantity of current SO lines, based on their related
analytic lines.
diff --git a/addons/sale/report/ir_actions_report_templates.xml b/addons/sale/report/ir_actions_report_templates.xml
index b6b49bd20a0..f0dd04defa4 100644
--- a/addons/sale/report/ir_actions_report_templates.xml
+++ b/addons/sale/report/ir_actions_report_templates.xml
@@ -63,7 +63,8 @@
-
| - | + | @@ -126,7 +127,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Subtotal
diff --git a/addons/sale/tests/test_sale_refund.py b/addons/sale/tests/test_sale_refund.py
index d51495cead3..a20964bc885 100644
--- a/addons/sale/tests/test_sale_refund.py
+++ b/addons/sale/tests/test_sale_refund.py
@@ -344,7 +344,8 @@ class TestSaleRefund(TestSaleCommon):
})
downpayment.create_invoices()
sale_order_refund.invoice_ids[0].action_post()
- sol_downpayment = sale_order_refund.order_line[1]
+ # order_line[1] is the down payment section
+ sol_downpayment = sale_order_refund.order_line[2]
payment = self.env['sale.advance.payment.inv'].with_context(so_context).create({
'deposit_account_id': self.company_data['default_account_revenue'].id
@@ -352,7 +353,8 @@ class TestSaleRefund(TestSaleCommon):
payment.create_invoices()
so_invoice = max(sale_order_refund.invoice_ids)
- self.assertEqual(len(so_invoice.invoice_line_ids.filtered(lambda l: not (l.display_type == 'line_section' and l.name == "Down Payments"))), len(sale_order_refund.order_line), 'All lines should be invoiced')
+ self.assertEqual(len(so_invoice.invoice_line_ids.filtered(lambda l: not (l.display_type == 'line_section' and l.name == "Down Payments"))),
+ len(sale_order_refund.order_line.filtered(lambda l: not (l.display_type == 'line_section' and l.name == "Down Payments"))), 'All lines should be invoiced')
self.assertEqual(len(so_invoice.invoice_line_ids.filtered(lambda l: l.display_type == 'line_section' and l.name == "Down Payments")), 1, 'A single section for downpayments should be present')
self.assertEqual(so_invoice.amount_total, sale_order_refund.amount_total - sol_downpayment.price_unit, 'Downpayment should be applied')
so_invoice.action_post()
diff --git a/addons/sale/tests/test_sale_to_invoice.py b/addons/sale/tests/test_sale_to_invoice.py
index 7072ef85587..be7c6d2d29b 100644
--- a/addons/sale/tests/test_sale_to_invoice.py
+++ b/addons/sale/tests/test_sale_to_invoice.py
@@ -108,7 +108,7 @@ class TestSaleToInvoice(TestSaleCommon):
self._check_order_search(self.sale_order, [('invoice_ids', '=', False)], self.env['sale.order'])
self.assertEqual(len(self.sale_order.invoice_ids), 2, 'Invoice should be created for the SO')
- downpayment_line = self.sale_order.order_line.filtered(lambda l: l.is_downpayment)
+ downpayment_line = self.sale_order.order_line.filtered(lambda l: l.is_downpayment and not l.display_type)
self.assertEqual(len(downpayment_line), 2, 'SO line downpayment should be created on SO')
# Update delivered quantity of SO lines
@@ -124,7 +124,8 @@ class TestSaleToInvoice(TestSaleCommon):
self.assertEqual(len(self.sale_order.invoice_ids), 3, 'Invoice should be created for the SO')
invoice = max(self.sale_order.invoice_ids)
- self.assertEqual(len(invoice.invoice_line_ids.filtered(lambda l: not (l.display_type == 'line_section' and l.name == "Down Payments"))), len(self.sale_order.order_line), 'All lines should be invoiced')
+ self.assertEqual(len(invoice.invoice_line_ids.filtered(lambda l: not (l.display_type == 'line_section' and l.name == "Down Payments"))),
+ len(self.sale_order.order_line.filtered(lambda l: not (l.display_type == 'line_section' and l.name == "Down Payments"))), 'All lines should be invoiced')
self.assertEqual(len(invoice.invoice_line_ids.filtered(lambda l: l.display_type == 'line_section' and l.name == "Down Payments")), 1, 'A single section for downpayments should be present')
self.assertEqual(invoice.amount_total, self.sale_order.amount_total - sum(downpayment_line.mapped('price_unit')), 'Downpayment should be applied')
@@ -147,7 +148,7 @@ class TestSaleToInvoice(TestSaleCommon):
payment.create_invoices()
self.assertEqual(len(self.sale_order.invoice_ids), 1, 'Invoice should be created for the SO')
- downpayment_line = self.sale_order.order_line.filtered(lambda l: l.is_downpayment)
+ downpayment_line = self.sale_order.order_line.filtered(lambda l: l.is_downpayment and not l.display_type)
self.assertEqual(len(downpayment_line), 1, 'SO line downpayment should be created on SO')
self.assertEqual(downpayment_line.price_unit, self.sale_order.amount_total/2, 'downpayment should have the correct amount')
diff --git a/addons/sale/views/sale_order_views.xml b/addons/sale/views/sale_order_views.xml
index 4230545c5bc..bf87909c5b3 100644
--- a/addons/sale/views/sale_order_views.xml
+++ b/addons/sale/views/sale_order_views.xml
@@ -503,8 +503,9 @@
optional="show"
/>
| - | + | @@ -537,8 +538,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Subtotal diff --git a/addons/sale/wizard/sale_make_invoice_advance.py b/addons/sale/wizard/sale_make_invoice_advance.py index 284bbfd03d9..04eb414df79 100644 --- a/addons/sale/wizard/sale_make_invoice_advance.py +++ b/addons/sale/wizard/sale_make_invoice_advance.py @@ -136,10 +136,25 @@ class SaleAdvancePaymentInv(models.TransientModel): subtype_id=self.env.ref('mail.mt_note').id) return invoice + def _prepare_downpayment_section(self, order): + context = {'lang': order.partner_id.lang} + + so_values = { + 'name': _('Down Payments'), + 'product_uom_qty': 0.0, + 'order_id': order.id, + 'display_type': 'line_section', + 'is_downpayment': True, + 'sequence': order.order_line and order.order_line[-1].sequence + 1 or 10, + } + + del context + return so_values + def _prepare_so_line(self, order, analytic_tag_ids, tax_ids, amount): context = {'lang': order.partner_id.lang} so_values = { - 'name': _('Down Payment: %s') % (time.strftime('%m %Y'),), + 'name': _('Down Payment: %s (Draft)') % (time.strftime('%m %Y'),), 'price_unit': amount, 'product_uom_qty': 0.0, 'order_id': order.id, @@ -179,6 +194,11 @@ class SaleAdvancePaymentInv(models.TransientModel): for line in order.order_line: analytic_tag_ids = [(4, analytic_tag.id, None) for analytic_tag in line.analytic_tag_ids] + # verify that no down payment section already exists + if not any(line.display_type and line.is_downpayment for line in order.order_line): + so_downpayment_section_values = self._prepare_downpayment_section(order) + sale_line_obj.create(so_downpayment_section_values) + so_line_values = self._prepare_so_line(order, analytic_tag_ids, tax_ids, amount) so_line = sale_line_obj.create(so_line_values) invoices += self._create_invoice(order, so_line, amount) |