[IMP] sale: down payment tax breakdown

Break down payments down per tax. When creating a down payment for a sale order, create one line per tax instead of one line for the enire order.

[Sale Order]
Product    Qty  Unit price    Tax   Subtotal
Product1   1    1000          10%   1000
Product2   1    1000          5%    1000
                                    ====
                            Untaxed 2000
                                Tax 150
                                    ====
                              Total 2150

Make a 50% (1000) down payment :

[BEFORE]
Product    Qty  Unit price    Tax   Subtotal
Product1   1    1000          10%   1000
Product2   1    1000          5%    1000
Down pay   1    1075                0
                                    ====
                            Untaxed 2000
                                Tax 150
                                    ====
                              Total 2150

[AFTER]
Product    Qty  Unit price    Tax   Subtotal
Product1   1    1000          10%   1000
Product2   1    1000          5%    1000
Down pay   1    500           10%   0
Down pay   1    500           5%    0
                                    ====
                            Untaxed 2000
                                Tax 150
                                    ====
                              Total 2150

closes odoo/odoo#105177

Task: 2678531
Related: odoo/enterprise#36331
Signed-off-by: Laurent Smet <las@odoo.com>
This commit is contained in:
jbw-odoo
2023-02-14 13:13:47 +01:00
parent 7510dc477d
commit a11f402d6b
6 changed files with 512 additions and 42 deletions
+3 -2
View File
@@ -1022,8 +1022,9 @@ class SaleOrder(models.Model):
'invoice_line_ids': [],
}
def action_view_invoice(self):
invoices = self.mapped('invoice_ids')
def action_view_invoice(self, invoices=False):
if not invoices:
invoices = self.mapped('invoice_ids')
action = self.env['ir.actions.actions']._for_xml_id('account.action_move_out_invoice_type')
if len(invoices) > 1:
action['domain'] = [('id', 'in', invoices.ids)]
+2 -1
View File
@@ -572,7 +572,7 @@ class SaleOrderLine(models.Model):
# otherwise it's a surcharge which shouldn't be shown to the customer
line.discount = discount
def _convert_to_tax_base_line_dict(self):
def _convert_to_tax_base_line_dict(self, **kwargs):
""" Convert the current record to a dictionary in order to use the generic taxes computation method
defined on account.tax.
@@ -589,6 +589,7 @@ class SaleOrderLine(models.Model):
quantity=self.product_uom_qty,
discount=self.discount,
price_subtotal=self.price_subtotal,
**kwargs,
)
@api.depends('product_uom_qty', 'discount', 'price_unit', 'tax_id')
+1
View File
@@ -16,3 +16,4 @@ from . import test_sale_product_attribute_value_config
from . import test_sale_refund
from . import test_sale_tax_totals
from . import test_sale_to_invoice
from . import test_sale_order_down_payment
@@ -0,0 +1,405 @@
from odoo.tests import tagged
from odoo import Command
from .common import TestSaleCommon
@tagged('post_install', '-at_install')
class TestSaleOrderDownPayment(TestSaleCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
SaleOrder = cls.env['sale.order'].with_context(tracking_disable=True)
cls.tax_10 = cls.create_tax(10)
cls.tax_15 = cls.create_tax(15)
# create a generic Sale Order with all classical products and empty pricelist
cls.sale_order = SaleOrder.create({
'partner_id': cls.partner_a.id,
'partner_invoice_id': cls.partner_a.id,
'partner_shipping_id': cls.partner_a.id,
'pricelist_id': cls.company_data['default_pricelist'].id,
})
cls.sol_product_order = cls.env['sale.order.line'].create({
'name': cls.company_data['product_order_no'].name,
'product_id': cls.company_data['product_order_no'].id,
'product_uom_qty': 2,
'product_uom': cls.company_data['product_order_no'].uom_id.id,
'price_unit': 100,
'order_id': cls.sale_order.id,
'tax_id': False,
})
cls.sol_serv_deliver = cls.env['sale.order.line'].create({
'name': cls.company_data['product_service_delivery'].name,
'product_id': cls.company_data['product_service_delivery'].id,
'product_uom_qty': 2,
'product_uom': cls.company_data['product_service_delivery'].uom_id.id,
'price_unit': 100,
'order_id': cls.sale_order.id,
'tax_id': False,
})
cls.sol_serv_order = cls.env['sale.order.line'].create({
'name': cls.company_data['product_service_order'].name,
'product_id': cls.company_data['product_service_order'].id,
'product_uom_qty': 2,
'product_uom': cls.company_data['product_service_order'].uom_id.id,
'price_unit': 100,
'order_id': cls.sale_order.id,
'tax_id': False,
})
cls.sol_product_deliver = cls.env['sale.order.line'].create({
'name': cls.company_data['product_delivery_no'].name,
'product_id': cls.company_data['product_delivery_no'].id,
'product_uom_qty': 2,
'product_uom': cls.company_data['product_delivery_no'].uom_id.id,
'price_unit': 100,
'order_id': cls.sale_order.id,
'tax_id': False,
})
cls.revenue_account = cls.company_data['default_account_revenue']
cls.receivable_account = cls.company_data['default_account_receivable']
@classmethod
def create_tax(cls, amount, values=None):
vals = {
'name': 'Tax %s' % amount,
'amount_type': 'percent',
'amount': amount,
'type_tax_use': 'sale',
}
if values:
vals.update(values)
return cls.env['account.tax'].create(vals)
@classmethod
def make_downpayment(cls, **kwargs):
so_context = {
'active_model': 'sale.order',
'active_ids': [cls.sale_order.id],
'active_id': cls.sale_order.id,
'default_journal_id': cls.company_data['default_journal_sale'].id,
}
payment_params = {
'advance_payment_method': 'percentage',
'amount': 50,
'deposit_account_id': cls.revenue_account.id,
**kwargs,
}
downpayment = cls.env['sale.advance.payment.inv'].with_context(so_context).create(payment_params)
downpayment.create_invoices()
cls.sale_order.action_confirm()
def _assert_invoice_lines_values(self, lines, expected):
return self.assertRecordValues(lines, [dict(zip(expected[0], x)) for x in expected[1:]])
def test_tax_breakdown(self):
self.sale_order.order_line[0].tax_id = self.tax_15 + self.tax_10
self.sale_order.order_line[1].tax_id = self.tax_10
self.sale_order.order_line[2].tax_id = self.tax_10
self.make_downpayment()
invoice = self.sale_order.invoice_ids
down_pay_amt = self.sale_order.amount_total / 2
# pylint: disable=C0326
expected = [
# keys
['account_id', 'tax_ids', 'balance', 'price_total'],
# base lines
[self.revenue_account.id, (self.tax_15 + self.tax_10).ids, -100, 125 ],
[self.revenue_account.id, self.tax_10.ids, -200, 220 ],
[self.revenue_account.id, self.env['account.tax'], -100, 100 ],
# taxes
[self.revenue_account.id, self.env['account.tax'], -30, 0 ],
[self.revenue_account.id, self.env['account.tax'], -15, 0 ],
# receivable
[self.receivable_account.id, self.env['account.tax'], down_pay_amt, 0 ],
]
self._assert_invoice_lines_values(invoice.line_ids, expected)
def test_tax_breakdown_other_currency(self):
self.sale_order.currency_id = self.currency_data['currency'] # rate = 2.0
self.sale_order.order_line[0].tax_id = self.tax_15 + self.tax_10
self.sale_order.order_line[1].tax_id = self.tax_10
self.sale_order.order_line[2].tax_id = self.tax_10
self.make_downpayment()
invoice = self.sale_order.invoice_ids
down_pay_amt = self.sale_order.amount_total / 2
# pylint: disable=C0326
expected = [
# keys
['account_id', 'tax_ids', 'balance', 'price_total'],
# base lines
[self.revenue_account.id, (self.tax_15 + self.tax_10).ids, -50, 125 ],
[self.revenue_account.id, self.tax_10.ids, -100, 220 ],
[self.revenue_account.id, self.env['account.tax'], -50, 100 ],
# taxes
[self.revenue_account.id, self.env['account.tax'], -15, 0 ],
[self.revenue_account.id, self.env['account.tax'], -7.5, 0 ],
# receivable
[self.receivable_account.id, self.env['account.tax'], down_pay_amt / 2.0, 0 ],
]
self._assert_invoice_lines_values(invoice.line_ids, expected)
def test_tax_breakdown_fixed_payment_method(self):
self.sale_order.order_line[0].tax_id = self.tax_15 + self.tax_10
self.sale_order.order_line[1].tax_id = self.tax_10
self.sale_order.order_line[2].tax_id = self.tax_10
self.make_downpayment(advance_payment_method='fixed', fixed_amount=222.5, amount=0)
invoice = self.sale_order.invoice_ids
down_pay_amt = 222.5
# pylint: disable=C0326
expected = [
# keys
['account_id', 'tax_ids', 'balance', 'price_total'],
# base lines
[self.revenue_account.id, (self.tax_15 + self.tax_10).ids, -50, 62.5 ],
[self.revenue_account.id, self.tax_10.ids, -100, 110 ],
[self.revenue_account.id, self.env['account.tax'], -50, 50 ],
# taxes
[self.revenue_account.id, self.env['account.tax'], -15, 0 ],
[self.revenue_account.id, self.env['account.tax'], -7.5, 0 ],
# receivable
[self.receivable_account.id, self.env['account.tax'], down_pay_amt, 0 ],
]
self._assert_invoice_lines_values(invoice.line_ids, expected)
def test_tax_breakdown_fixed_payment_method_with_taxes_on_all_lines(self):
self.sale_order.order_line[0].tax_id = self.tax_15
self.sale_order.order_line[1].tax_id = self.tax_10
self.sale_order.order_line[2].tax_id = self.tax_10
self.sale_order.order_line[3].tax_id = self.tax_10
self.make_downpayment(advance_payment_method='fixed', fixed_amount=222.5, amount=0)
invoice = self.sale_order.invoice_ids
down_pay_amt = 222.5
# pylint: disable=C0326
expected = [
# keys
['account_id', 'tax_ids', 'balance', 'price_total'],
# base lines
[self.revenue_account.id, self.tax_15.ids, -50, 57.5 ],
[self.revenue_account.id, self.tax_10.ids, -150, 165 ],
# taxes
[self.revenue_account.id, self.env['account.tax'], -7.5, 0 ],
[self.revenue_account.id, self.env['account.tax'], -15, 0 ],
# receivable
[self.receivable_account.id, self.env['account.tax'], down_pay_amt, 0 ],
]
self._assert_invoice_lines_values(invoice.line_ids, expected)
def test_tax_price_include_breakdown(self):
tax_10_incl = self.create_tax(10, {'price_include': True})
self.sale_order.order_line[0].tax_id = tax_10_incl + self.tax_10
self.sale_order.order_line[1].tax_id = self.tax_10
self.sale_order.order_line[2].tax_id = self.tax_10
self.make_downpayment()
invoice = self.sale_order.invoice_ids
down_pay_amt = self.sale_order.amount_total / 2
# pylint: disable=C0326
expected = [
# keys
['account_id', 'tax_ids', 'balance', 'price_total'],
# base lines
[self.revenue_account.id, (tax_10_incl + self.tax_10).ids, -90.91, 109.09 ],
[self.revenue_account.id, self.tax_10.ids, -200, 220 ],
[self.revenue_account.id, self.env['account.tax'], -100, 100 ],
# taxes
[self.revenue_account.id, self.env['account.tax'], -29.09, 0 ],
[self.revenue_account.id, self.env['account.tax'], -9.09, 0 ],
# receivable
[self.receivable_account.id, self.env['account.tax'], down_pay_amt, 0 ],
]
self._assert_invoice_lines_values(invoice.line_ids, expected)
def test_tax_price_include_include_base_amount_breakdown(self):
tax_10_pi_ba = self.create_tax(10, {'price_include': True, 'include_base_amount': True})
self.tax_10.sequence = 2
self.sale_order.order_line[0].tax_id = tax_10_pi_ba + self.tax_10
self.sale_order.order_line[1].tax_id = self.tax_10
self.sale_order.order_line[2].tax_id = self.tax_10
self.make_downpayment()
invoice = self.sale_order.invoice_ids
down_pay_amt = self.sale_order.amount_total / 2
# pylint: disable=C0326
expected = [
# keys
['account_id', 'tax_ids', 'balance', 'price_total'],
# base lines
[self.revenue_account.id, (tax_10_pi_ba + self.tax_10).ids, -90.91, 110 ],
[self.revenue_account.id, self.tax_10.ids, -200, 220 ],
[self.revenue_account.id, self.env['account.tax'], -100, 100 ],
# taxes
[self.revenue_account.id, self.tax_10.ids, -9.09, 0 ],
[self.revenue_account.id, self.env['account.tax'], -30, 0 ],
# receivable
[self.receivable_account.id, self.env['account.tax'], down_pay_amt, 0 ],
]
self._assert_invoice_lines_values(invoice.line_ids, expected)
def test_tax_breakdown_with_discount(self):
self.sale_order.order_line[0].tax_id = self.tax_10
self.sale_order.order_line[1].tax_id = self.tax_10
self.sale_order.order_line[1].discount = 25.0
self.sale_order.order_line[2].tax_id = self.tax_15
self.make_downpayment()
invoice = self.sale_order.invoice_ids
down_pay_amt = self.sale_order.amount_total / 2
# pylint: disable=C0326
expected = [
# keys
['account_id', 'tax_ids', 'balance', 'price_total' ],
# base lines
[self.revenue_account.id, self.tax_10.ids, -175, 192.5 ],
[self.revenue_account.id, self.tax_15.ids, -100, 115 ],
[self.revenue_account.id, self.env['account.tax'], -100, 100 ],
# taxes
[self.revenue_account.id, self.env['account.tax'], -17.5, 0 ],
[self.revenue_account.id, self.env['account.tax'], -15, 0 ],
# receivable
[self.receivable_account.id, self.env['account.tax'], down_pay_amt, 0 ],
]
self._assert_invoice_lines_values(invoice.line_ids, expected)
def test_tax_price_include_include_base_amount_breakdown_with_discount(self):
tax_10_pi_ba = self.create_tax(10, {'price_include': True, 'include_base_amount': True})
self.tax_10.sequence = 2
self.sale_order.order_line[0].tax_id = tax_10_pi_ba + self.tax_10
self.sale_order.order_line[0].discount = 25.0
self.sale_order.order_line[1].tax_id = self.tax_10
self.sale_order.order_line[2].tax_id = self.tax_10
self.make_downpayment()
invoice = self.sale_order.invoice_ids
down_pay_amt = self.sale_order.amount_total / 2
# pylint: disable=C0326
expected = [
# keys
['account_id', 'tax_ids', 'balance', 'price_total'],
# base lines
[self.revenue_account.id, (tax_10_pi_ba + self.tax_10).ids, -68.18, 82.5 ],
[self.revenue_account.id, self.tax_10.ids, -200, 220 ],
[self.revenue_account.id, self.env['account.tax'], -100, 100 ],
# taxes
[self.revenue_account.id, self.tax_10.ids, -6.82, 0 ],
[self.revenue_account.id, self.env['account.tax'], -27.5, 0 ],
# receivable
[self.receivable_account.id, self.env['account.tax'], down_pay_amt, 0 ],
]
self._assert_invoice_lines_values(invoice.line_ids, expected)
def test_tax_fixed_amount_breakdown(self):
tax_10_fix_a = self.create_tax(10, {'amount_type': 'fixed', 'include_base_amount': True})
tax_10_fix_b = self.create_tax(10, {'amount_type': 'fixed', 'include_base_amount': True})
tax_10_fix_c = self.create_tax(10, {'amount_type': 'fixed'})
tax_10_a = self.tax_10
tax_10_b = self.create_tax(10)
tax_group_1 = self.env['account.tax'].create({
'name': "Tax Group",
'amount_type': 'group',
'children_tax_ids': [Command.set((tax_10_fix_a + tax_10_a + tax_10_fix_b + tax_10_b).ids)],
'type_tax_use': 'sale',
})
tax_group_2 = self.env['account.tax'].create({
'name': "Tax Group 2",
'amount_type': 'group',
'children_tax_ids': [Command.set((tax_10_fix_c + tax_10_a).ids)],
'type_tax_use': 'sale',
})
self.sale_order.order_line[0].tax_id = tax_group_1
self.sale_order.order_line[1].tax_id = tax_group_2
self.sale_order.order_line[2].tax_id = tax_10_a
self.make_downpayment()
invoice = self.sale_order.invoice_ids
down_pay_amt = self.sale_order.amount_total / 2
# pylint: disable=C0326
expected = [
# keys
['account_id', 'tax_ids', 'balance', 'price_total'],
# base lines
[self.revenue_account.id, (tax_10_a + tax_10_b).ids, -110, 132 ],
[self.revenue_account.id, tax_10_b.ids, -10, 11 ],
[self.revenue_account.id, tax_10_a.ids, -200, 220 ],
[self.revenue_account.id, self.env['account.tax'], -110, 110 ],
# taxes
[self.revenue_account.id, self.env['account.tax'], -31, 0 ],
[self.revenue_account.id, self.env['account.tax'], -12, 0 ],
# receivable
[self.receivable_account.id, self.env['account.tax'], down_pay_amt, 0 ],
]
self._assert_invoice_lines_values(invoice.line_ids, expected)
def test_analytic_distribution(self):
analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
an_acc_01 = str(self.env['account.analytic.account'].create({'name': 'Account 01', 'plan_id': analytic_plan.id}).id)
an_acc_02 = str(self.env['account.analytic.account'].create({'name': 'Account 02', 'plan_id': analytic_plan.id}).id)
self.sale_order.order_line[0].tax_id = self.tax_15 + self.tax_10
self.sale_order.order_line[0].analytic_distribution = {an_acc_01: 100}
self.sale_order.order_line[1].tax_id = self.tax_10
self.sale_order.order_line[1].analytic_distribution = {an_acc_01: 50, an_acc_02: 50}
self.sale_order.order_line[2].tax_id = self.tax_10
self.sale_order.order_line[2].analytic_distribution = {an_acc_01: 100}
self.make_downpayment()
invoice = self.sale_order.invoice_ids
down_pay_amt = self.sale_order.amount_total / 2
# pylint: disable=C0326
expected = [
# keys
['account_id', 'tax_ids', 'balance', 'price_total', 'analytic_distribution' ],
# base lines
[self.revenue_account.id, (self.tax_15 + self.tax_10).ids, -100, 125, {an_acc_01: 100} ],
[self.revenue_account.id, self.tax_10.ids, -100, 110, {an_acc_01: 50, an_acc_02: 50}],
[self.revenue_account.id, self.tax_10.ids, -100, 110, {an_acc_01: 100}],
[self.revenue_account.id, self.env['account.tax'], -100, 100 , False ],
# taxes
[self.revenue_account.id, self.env['account.tax'], -30, 0, False ],
[self.revenue_account.id, self.env['account.tax'], -15, 0, False ],
# receivable
[self.receivable_account.id, self.env['account.tax'], down_pay_amt, 0, False ],
]
self._assert_invoice_lines_values(invoice.line_ids, expected)
def test_tax_fixed_amount_analytic_distribution(self):
analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
an_acc_01 = str(self.env['account.analytic.account'].create({'name': 'Account 01', 'plan_id': analytic_plan.id}).id)
an_acc_02 = str(self.env['account.analytic.account'].create({'name': 'Account 02', 'plan_id': analytic_plan.id}).id)
tax_10_fix_a = self.create_tax(10, {'amount_type': 'fixed', 'include_base_amount': True})
tax_10_fix_b = self.create_tax(10, {'amount_type': 'fixed', 'include_base_amount': True})
tax_10_fix_c = self.create_tax(10, {'amount_type': 'fixed'})
tax_10_a = self.tax_10
tax_10_b = self.create_tax(10)
tax_group_1 = self.env['account.tax'].create({
'name': "Tax Group",
'amount_type': 'group',
'children_tax_ids': [Command.set((tax_10_fix_a + tax_10_a + tax_10_fix_b + tax_10_b).ids)],
'type_tax_use': 'sale',
})
tax_group_2 = self.env['account.tax'].create({
'name': "Tax Group 2",
'amount_type': 'group',
'children_tax_ids': [Command.set((tax_10_fix_c + tax_10_a).ids)],
'type_tax_use': 'sale',
})
self.sale_order.order_line[0].tax_id = tax_group_1
self.sale_order.order_line[0].analytic_distribution = {an_acc_01: 50, an_acc_02: 50}
self.sale_order.order_line[1].tax_id = tax_group_2
self.sale_order.order_line[2].tax_id = tax_10_a
self.make_downpayment()
invoice = self.sale_order.invoice_ids
down_pay_amt = self.sale_order.amount_total / 2
# pylint: disable=C0326
expected = [
# keys
['account_id', 'tax_ids', 'balance', 'price_total', 'analytic_distribution'],
# base lines
[self.revenue_account.id, (tax_10_a + tax_10_b).ids, -110, 132, {an_acc_01: 50, an_acc_02: 50}],
[self.revenue_account.id, tax_10_b.ids, -10, 11, {an_acc_01: 50, an_acc_02: 50}],
[self.revenue_account.id, tax_10_a.ids, -200, 220, False ],
[self.revenue_account.id, self.env['account.tax'], -110, 110, False ],
# taxes
[self.revenue_account.id, self.env['account.tax'], -31, 0, False ],
[self.revenue_account.id, self.env['account.tax'], -12, 0, False ],
# receivable
[self.receivable_account.id, self.env['account.tax'], down_pay_amt, 0, False ],
]
self._assert_invoice_lines_values(invoice.line_ids, expected)
+5 -3
View File
@@ -403,6 +403,7 @@
<field name="product_uom_category_id" invisible="1"/>
<field name="product_type" invisible="1"/>
<field name="product_updatable" invisible="1"/>
<field name="is_downpayment" invisible="1"/>
<field
name="product_id"
attrs="{
@@ -461,6 +462,7 @@
'uom': product_uom,
'company_id': parent.company_id
}"
attrs="{'readonly': [('is_downpayment', '==', True)]}"
/>
<field
name="qty_delivered"
@@ -468,7 +470,7 @@
string="Delivered"
attrs="{
'column_invisible': [('parent.state', 'not in', ['sale', 'done'])],
'readonly': [('qty_delivered_method', '!=', 'manual')]
'readonly': ['|', ('qty_delivered_method', '!=', 'manual'), ('is_downpayment', '=', True)]
}"
optional="show"
/>
@@ -499,7 +501,7 @@
<field
name="customer_lead"
optional="hide"
attrs="{'readonly': [('parent.state', 'not in', ['draft', 'sent', 'sale'])]}"
attrs="{'readonly': ['|', ('parent.state', 'not in', ['draft', 'sent', 'sale']), ('is_downpayment', '==', True)]}"
/>
<field name="product_packaging_qty" attrs="{'invisible': ['|', ('product_id', '=', False), ('product_packaging_id', '=', False)]}" groups="product.group_stock_packaging" optional="show"/>
<field name="product_packaging_id" attrs="{'invisible': [('product_id', '=', False)]}" context="{'default_product_id': product_id, 'tree_view_ref':'product.product_packaging_tree_view', 'form_view_ref':'product.product_packaging_form_view'}" groups="product.group_stock_packaging" optional="show"/>
@@ -513,7 +515,7 @@
options="{'no_create': True}"
domain="[('type_tax_use','=','sale'),('company_id','=',parent.company_id), ('country_id', '=', parent.tax_country_id)]"
context="{'active_test': True}"
attrs="{'readonly': [('qty_invoiced', '&gt;', 0)]}"
attrs="{'readonly': ['|', ('qty_invoiced', '&gt;', 0), ('is_downpayment', '==', True)]}"
optional="show"
/>
<field name="discount" string="Disc.%" groups="product.group_discount_per_so_line" optional="show" widget="sol_discount"/>
+96 -36
View File
@@ -6,7 +6,7 @@ import time
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.fields import Command
from odoo.tools import float_is_zero
from odoo.tools import frozendict
class SaleAdvancePaymentInv(models.TransientModel):
@@ -153,10 +153,9 @@ class SaleAdvancePaymentInv(models.TransientModel):
#=== ACTION METHODS ===#
def create_invoices(self):
self._create_invoices(self.sale_order_ids)
invoices = self._create_invoices(self.sale_order_ids)
if self.env.context.get('open_invoices'):
return self.sale_order_ids.action_view_invoice()
return self.sale_order_ids.action_view_invoice(invoices=invoices)
return {'type': 'ir.actions.act_window_close'}
@@ -184,14 +183,32 @@ class SaleAdvancePaymentInv(models.TransientModel):
self._prepare_down_payment_section_values(order)
)
down_payment_so_line = self.env['sale.order.line'].create(
self._prepare_so_line_values(order)
down_payment_lines = self.env['sale.order.line'].create(
self._prepare_down_payment_lines_values(order)
)
invoice = self.env['account.move'].sudo().create(
self._prepare_invoice_values(order, down_payment_so_line)
self._prepare_invoice_values(order, down_payment_lines)
).with_user(self.env.uid) # Unsudo the invoice after creation
# Ensure the invoice total is exactly the expected fixed amount.
if self.advance_payment_method == 'fixed':
delta_amount = (invoice.amount_total - self.fixed_amount) * (1 if invoice.is_inbound() else -1)
if not order.currency_id.is_zero(delta_amount):
tax_line = invoice.line_ids\
.filtered(lambda aml: aml.tax_line_id.amount_type not in (False, 'fixed'))[:1]
receivable_line = invoice.line_ids\
.filtered(lambda aml: aml.account_id.account_type == 'asset_receivable')[:1]
if tax_line and receivable_line:
invoice.line_ids = [
Command.update(tax_line.id, {
'amount_currency': tax_line.amount_currency + delta_amount,
}),
Command.update(receivable_line.id, {
'amount_currency': receivable_line.amount_currency + delta_amount,
}),
]
invoice.message_post_with_source(
'mail.message_origin_link',
render_values={'self': invoice, 'origin': order},
@@ -226,52 +243,96 @@ class SaleAdvancePaymentInv(models.TransientModel):
del context
return so_values
def _prepare_so_line_values(self, order):
def _prepare_down_payment_lines_values(self, order):
""" Create one down payment line per tax or unique taxes combination.
Apply the tax(es) to their respective lines.
:param order: Order for which the down payment lines are created.
:return: An array of dicts with the down payment lines values.
"""
self.ensure_one()
if self.advance_payment_method == 'percentage':
percentage = self.amount / 100
else:
percentage = self.fixed_amount / order.amount_total
order_lines = order.order_line.filtered(lambda l: not l.display_type)
base_downpayment_lines_values = self._prepare_base_downpayment_line_values(order)
tax_base_line_dicts = [line._convert_to_tax_base_line_dict(
analytic_distribution=line.analytic_distribution) for line in order_lines]
computed_taxes = self.env['account.tax']._compute_taxes(
tax_base_line_dicts,
handle_price_include=False
)
down_payment_values = []
for line, tax_repartition in computed_taxes['base_lines_to_update']:
taxes = line['taxes'].flatten_taxes_hierarchy()
fixed_taxes = taxes.filtered(lambda tax: tax.amount_type == 'fixed')
down_payment_values.append([
taxes - fixed_taxes,
line['analytic_distribution'],
tax_repartition['price_subtotal']
])
for fixed_tax in fixed_taxes:
# Fixed taxes cannot be set as taxes on down payments as they always amounts to 100%
# of the tax amount. Therefore fixed taxes are removed and are replace by a new line
# with appropriate amount, and non fixed taxes if the fixed tax affected the base of
# any other non fixed tax.
if fixed_tax.include_base_amount:
pct_tax = taxes[list(taxes).index(fixed_tax) + 1:]\
.filtered(lambda t: t.is_base_affected and t.amount_type != 'fixed')
else:
pct_tax = self.env['account.tax']
down_payment_values.append([
pct_tax,
line['analytic_distribution'],
line['quantity'] * fixed_tax.amount
])
downpayment_line_map = {}
for tax_id, analytic_distribution, price_subtotal in down_payment_values:
grouping_key = frozendict({
'tax_id': tuple(sorted(tax_id.ids)),
'analytic_distribution': analytic_distribution,
})
downpayment_line_map.setdefault(grouping_key, {
**base_downpayment_lines_values,
**grouping_key,
'product_uom_qty': 0.0,
'price_unit': 0.0,
})
downpayment_line_map[grouping_key]['price_unit'] += \
order.currency_id.round(price_subtotal * percentage)
return list(downpayment_line_map.values())
def _prepare_base_downpayment_line_values(self, order):
self.ensure_one()
analytic_distribution = {}
amount_total = sum(order.order_line.mapped("price_total"))
if not float_is_zero(amount_total, precision_rounding=self.currency_id.rounding):
for line in order.order_line:
distrib_dict = line.analytic_distribution or {}
for account, distribution in distrib_dict.items():
analytic_distribution[account] = distribution * line.price_total + analytic_distribution.get(account, 0)
for account, distribution_amount in analytic_distribution.items():
analytic_distribution[account] = distribution_amount/amount_total
context = {'lang': order.partner_id.lang}
so_values = {
'name': _('Down Payment: %s (Draft)', time.strftime('%m %Y')),
'price_unit': self._get_down_payment_amount(order),
'product_uom_qty': 0.0,
'order_id': order.id,
'discount': 0.0,
'product_id': self.product_id.id,
'analytic_distribution': analytic_distribution,
'is_downpayment': True,
'sequence': order.order_line and order.order_line[-1].sequence + 1 or 10,
}
del context
return so_values
def _get_down_payment_amount(self, order):
self.ensure_one()
if self.advance_payment_method == 'percentage':
amount = order.amount_total * self.amount / 100
else: # Fixed amount
amount = self.fixed_amount
return amount
def _prepare_invoice_values(self, order, so_line):
def _prepare_invoice_values(self, order, so_lines):
self.ensure_one()
return {
**order._prepare_invoice(),
'invoice_line_ids': [
Command.create(
so_line._prepare_invoice_line(
name=self._get_down_payment_description(order),
quantity=1.0,
)
'invoice_line_ids': [Command.create(
line._prepare_invoice_line(
name=self._get_down_payment_description(order),
quantity=1.0,
)
],
) for line in so_lines],
}
def _get_down_payment_description(self, order):
@@ -282,5 +343,4 @@ class SaleAdvancePaymentInv(models.TransientModel):
else:
name = _('Down Payment')
del context
return name