changeset 59093c3932e2 in modules/account_invoice:default
details: 
https://hg.tryton.org/modules/account_invoice?cmd=changeset&node=59093c3932e2
description:
        Add deductible rate of taxes to supplier invoice

        issue6911
        review341801002
diffstat:

 CHANGELOG                                 |    1 +
 company.py                                |    7 +
 doc/design.rst                            |    2 +
 invoice.py                                |   78 ++++++++++++++++++-
 tests/scenario_invoice_tax_deductible.rst |  115 ++++++++++++++++++++++++++++++
 tests/test_account_invoice.py             |    5 +
 view/company_form.xml                     |    2 +
 view/invoice_line_form.xml                |    8 +-
 8 files changed, 209 insertions(+), 9 deletions(-)

diffs (359 lines):

diff -r 7c198bad747f -r 59093c3932e2 CHANGELOG
--- a/CHANGELOG Sun Apr 11 18:08:27 2021 +0200
+++ b/CHANGELOG Sun Apr 11 18:11:05 2021 +0200
@@ -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
diff -r 7c198bad747f -r 59093c3932e2 company.py
--- a/company.py        Sun Apr 11 18:08:27 2021 +0200
+++ b/company.py        Sun Apr 11 18:11:05 2021 +0200
@@ -7,10 +7,17 @@
 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
diff -r 7c198bad747f -r 59093c3932e2 doc/design.rst
--- a/doc/design.rst    Sun Apr 11 18:08:27 2021 +0200
+++ b/doc/design.rst    Sun Apr 11 18:11:05 2021 +0200
@@ -42,6 +42,8 @@
 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.
diff -r 7c198bad747f -r 59093c3932e2 invoice.py
--- a/invoice.py        Sun Apr 11 18:08:27 2021 +0200
+++ b/invoice.py        Sun Apr 11 18:11:05 2021 +0200
@@ -1820,6 +1820,16 @@
             '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 @@
         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 @@
             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 @@
             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 @@
             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 @@
     @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 @@
                 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
diff -r 7c198bad747f -r 59093c3932e2 tests/scenario_invoice_tax_deductible.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/scenario_invoice_tax_deductible.rst Sun Apr 11 18:11:05 2021 +0200
@@ -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
diff -r 7c198bad747f -r 59093c3932e2 tests/test_account_invoice.py
--- a/tests/test_account_invoice.py     Sun Apr 11 18:08:27 2021 +0200
+++ b/tests/test_account_invoice.py     Sun Apr 11 18:11:05 2021 +0200
@@ -301,6 +301,11 @@
             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,
diff -r 7c198bad747f -r 59093c3932e2 view/company_form.xml
--- a/view/company_form.xml     Sun Apr 11 18:08:27 2021 +0200
+++ b/view/company_form.xml     Sun Apr 11 18:11:05 2021 +0200
@@ -4,6 +4,8 @@
 <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>
diff -r 7c198bad747f -r 59093c3932e2 view/invoice_line_form.xml
--- a/view/invoice_line_form.xml        Sun Apr 11 18:08:27 2021 +0200
+++ b/view/invoice_line_form.xml        Sun Apr 11 18:11:05 2021 +0200
@@ -22,10 +22,14 @@
             <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"/>

Reply via email to