[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:
@@ -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)]
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
@@ -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', '>', 0)]}"
|
||||
attrs="{'readonly': ['|', ('qty_invoiced', '>', 0), ('is_downpayment', '==', True)]}"
|
||||
optional="show"
|
||||
/>
|
||||
<field name="discount" string="Disc.%" groups="product.group_discount_per_so_line" optional="show" widget="sol_discount"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user