Add deductible rate of taxes to supplier invoice

issue6911
review341801002
This commit is contained in:
Cédric Krier
2021-04-11 18:11:05 +02:00
parent 7ed72c1d11
commit 913130c04f
8 changed files with 209 additions and 9 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add deductible rate of taxes to supplier invoice
* Add method to post invoices by batch
* Add payment term date in invoice
* Raise warning on invoice with maturity dates on the past
+7
View File
@@ -7,10 +7,17 @@ from trytond.pool import PoolMeta
class Company(metaclass=PoolMeta):
__name__ = 'company.company'
purchase_taxes_expense = fields.Boolean(
"Purchase Taxes as Expense",
help="Check to book purchase taxes as expense.")
cancel_invoice_out = fields.Boolean(
"Cancel Customer Invoice",
help="Allow cancelling move of customer invoice.")
@classmethod
def default_purchase_taxes_expense(cls):
return False
@classmethod
def default_cancel_invoice_out(cls):
return False
+2
View File
@@ -42,6 +42,8 @@ setting in the `Account Configuration <account:model-account.configuration>`.
Additional taxes can be manually added to the invoice when required.
It is also possible to change calculated tax amounts, although these changes
get overwritten if the invoice's tax amounts get recalculated.
For supplier invoice it is possible to define per line the deductible rate of
the taxes.
When an invoice is processed an `Account Move <account:model-account.move>` is
automatically created for it.
+71 -7
View File
@@ -1820,6 +1820,16 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
'readonly': _states['readonly'] | ~Bool(Eval('account')),
},
depends=['type', 'invoice_type', 'company', 'account'] + _depends)
taxes_deductible_rate = fields.Numeric(
"Taxes Deductible Rate", digits=(14, 10),
domain=[
('taxes_deductible_rate', '>=', 0),
('taxes_deductible_rate', '<=', 1),
],
states={
'invisible': Eval('invoice_type') != 'in',
},
depends=['invoice_type'])
taxes_date = fields.Date(
"Taxes Date",
states={
@@ -1908,11 +1918,24 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
return Invoice.fields_get(['type'])['type']['selection'] + [(None, '')]
@fields.depends(
'invoice', '_parent_invoice.currency', '_parent_invoice.company')
'invoice', '_parent_invoice.currency', '_parent_invoice.company',
'_parent_invoice.type',
methods=['on_change_company'])
def on_change_invoice(self):
if self.invoice:
self.currency = self.invoice.currency
self.company = self.invoice.company
self.on_change_company()
self.invoice_type = self.invoice.type
@fields.depends('company', 'invoice',
'_parent_invoice.type', 'invoice_type')
def on_change_company(self):
invoice_type = self.invoice.type if self.invoice else self.invoice_type
if (invoice_type == 'in'
and self.company
and self.company.purchase_taxes_expense):
self.taxes_deductible_rate = 0
@staticmethod
def default_currency():
@@ -1984,14 +2007,26 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
return self.currency.digits
return 2
@fields.depends('type', 'quantity', 'unit_price', 'invoice',
'_parent_invoice.currency', 'currency')
@fields.depends(
'type', 'quantity', 'unit_price', 'taxes_deductible_rate', 'invoice',
'_parent_invoice.currency', 'currency', 'taxes',
'_parent_invoice.type', 'invoice_type',
methods=['_get_taxes'])
def on_change_with_amount(self):
if self.type == 'line':
currency = (self.invoice.currency if self.invoice
else self.currency)
amount = (Decimal(str(self.quantity or '0.0'))
* (self.unit_price or Decimal('0.0')))
invoice_type = (
self.invoice.type if self.invoice else self.invoice_type)
if invoice_type == 'in' and self.taxes_deductible_rate != 1:
with Transaction().set_context(_deductible_rate=1):
tax_amount = sum(
t['amount'] for t in self._get_taxes().values())
non_deductible_amount = (
tax_amount * (1 - self.taxes_deductible_rate))
amount += non_deductible_amount
if currency:
return currency.round(amount)
return amount
@@ -2004,8 +2039,7 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
subtotal = Decimal(0)
for line2 in self.invoice.lines:
if line2.type == 'line':
subtotal += line2.invoice.currency.round(
Decimal(str(line2.quantity)) * line2.unit_price)
subtotal += line2.amount
elif line2.type == 'subtotal':
if self == line2:
break
@@ -2020,20 +2054,43 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
return self.origin.invoice.rec_name
return self.origin.rec_name if self.origin else None
@classmethod
def default_taxes_deductible_rate(cls):
return 1
@property
def taxable_lines(self):
# In case we're called from an on_change we have to use some sensible
# defaults
context = Transaction().context
if (getattr(self, 'invoice', None)
and getattr(self.invoice, 'type', None)):
invoice_type = self.invoice.type
else:
invoice_type = getattr(self, 'invoice_type', None)
if invoice_type == 'in':
if context.get('_deductible_rate') is not None:
deductible_rate = context['_deductible_rate']
else:
deductible_rate = getattr(self, 'taxes_deductible_rate', 1)
if not deductible_rate:
return []
else:
deductible_rate = 1
return [(
getattr(self, 'taxes', None) or [],
getattr(self, 'unit_price', None) or Decimal(0),
((getattr(self, 'unit_price', None) or Decimal(0))
* deductible_rate),
getattr(self, 'quantity', None) or 0,
getattr(self, 'tax_date', None),
)]
@property
def tax_date(self):
return self.taxes_date or self.invoice.tax_date
if getattr(self, 'taxes_date', None):
return self.taxes_date
elif hasattr(self, 'invoice') and hasattr(self.invoice, 'tax_date'):
return self.invoice.tax_date
def _get_tax_context(self):
if self.invoice:
@@ -2074,6 +2131,7 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
@fields.depends('product', 'unit', '_parent_invoice.type',
'_parent_invoice.party', 'party', 'invoice', 'invoice_type',
'_parent_invoice.invoice_date', '_parent_invoice.accounting_date',
'company',
methods=['_get_tax_rule_pattern'])
def on_change_product(self):
if not self.product:
@@ -2108,6 +2166,12 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
if tax_ids:
taxes.extend(tax_ids)
self.taxes = taxes
if self.company and self.company.purchase_taxes_expense:
self.taxes_deductible_rate = 0
else:
self.taxes_deductible_rate = (
self.product.supplier_taxes_deductible_rate_used)
else:
with Transaction().set_context(date=date):
self.account = self.product.account_revenue_used
+115
View File
@@ -0,0 +1,115 @@
======================
Invoice Tax Deductible
======================
Imports::
>>> import datetime as dt
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.tests.tools import activate_modules
>>> from trytond.modules.company.tests.tools import (
... create_company, get_company)
>>> from trytond.modules.account.tests.tools import (
... create_fiscalyear, create_chart, get_accounts, create_tax)
>>> from trytond.modules.account_invoice.tests.tools import (
... set_fiscalyear_invoice_sequences)
>>> today = dt.date.today()
Activate modules::
>>> config = activate_modules('account_invoice')
>>> Invoice = Model.get('account.invoice')
>>> Party = Model.get('party.party')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> ProductUom = Model.get('product.uom')
Create company::
>>> _ = create_company()
>>> company = get_company()
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(
... create_fiscalyear(company))
>>> fiscalyear.click('create_period')
Create chart of accounts::
>>> _ = create_chart(company)
>>> accounts = get_accounts(company)
Create tax::
>>> tax = create_tax(Decimal('.10'))
>>> tax.save()
Create party::
>>> party = Party(name="Party")
>>> party.save()
Create account category::
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_expense = accounts['expense']
>>> account_category.supplier_taxes_deductible_rate = Decimal('.5')
>>> account_category.supplier_taxes.append(tax)
>>> account_category.save()
Create product::
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'service'
>>> template.list_price = Decimal('100')
>>> template.account_category = account_category
>>> template.save()
>>> product, = template.products
Post a supplier invoice with 0% deductible::
>>> invoice = Invoice(type='in')
>>> invoice.party = party
>>> invoice.invoice_date = today
>>> line = invoice.lines.new()
>>> line.product = product
>>> line.quantity = 10
>>> line.unit_price = Decimal('50')
>>> line.taxes_deductible_rate
Decimal('0.5')
>>> line.taxes_deductible_rate = Decimal(0)
>>> line.amount
Decimal('550.00')
>>> invoice.untaxed_amount, invoice.tax_amount, invoice.total_amount
(Decimal('550.00'), Decimal('0.00'), Decimal('550.00'))
>>> invoice.click('post')
>>> invoice.untaxed_amount, invoice.tax_amount, invoice.total_amount
(Decimal('550.00'), Decimal('0'), Decimal('550.00'))
>>> len(invoice.taxes)
0
Post a supplier invoice with 50% deductible rate::
>>> invoice = Invoice(type='in')
>>> invoice.party = party
>>> invoice.invoice_date = today
>>> line = invoice.lines.new()
>>> line.product = product
>>> line.quantity = 10
>>> line.unit_price = Decimal('50')
>>> line.amount
Decimal('525.00')
>>> invoice.untaxed_amount, invoice.tax_amount, invoice.total_amount
(Decimal('525.00'), Decimal('25.00'), Decimal('550.00'))
>>> invoice.click('post')
>>> invoice.untaxed_amount, invoice.tax_amount, invoice.total_amount
(Decimal('525.00'), Decimal('25.00'), Decimal('550.00'))
>>> len(invoice.taxes)
1
+5
View File
@@ -301,6 +301,11 @@ def suite():
tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
suite.addTests(doctest.DocFileSuite(
'scenario_invoice_tax_deductible.rst',
tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
suite.addTests(doctest.DocFileSuite('scenario_invoice_group_line.rst',
tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
+2
View File
@@ -4,6 +4,8 @@ this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/form/notebook" position="inside">
<page string="Invoice" id="invoice">
<label name="purchase_taxes_expense"/>
<field name="purchase_taxes_expense"/>
<label name="cancel_invoice_out"/>
<field name="cancel_invoice_out"/>
</page>
+6 -2
View File
@@ -22,10 +22,14 @@ this repository contains the full copyright notices and license terms. -->
<field name="unit_price" symbol="currency"/>
<label name="amount"/>
<field name="amount" symbol="currency"/>
<field name="taxes" colspan="4"/>
<label name="taxes_date"/>
<field name="taxes_date"/>
<newline/>
<label name="taxes_deductible_rate"/>
<group col="2" id="taxes_deductible_rate">
<field name="taxes_deductible_rate" factor="100" xexpand="0"/>
<label name="taxes_deductible_rate" string="%" xalign="0.0" xexpand="1"/>
</group>
<field name="taxes" colspan="4"/>
<label name="origin"/>
<field name="origin" colspan="3"/>