changeset c72071b493f7 in modules/account_tax_rule_country:default
details: 
https://hg.tryton.org/modules/account_tax_rule_country?cmd=changeset;node=c72071b493f7
description:
        Add subdivision as criteria

        issue8252
        review257851003
diffstat:

 CHANGELOG                              |    2 +
 __init__.py                            |    1 +
 account.py                             |  113 +++++++++++++++++++++++----
 doc/index.rst                          |   22 ++--
 purchase.py                            |    8 +-
 sale.py                                |    8 +-
 stock.py                               |   26 ++++-
 tests/test_account_tax_rule_country.py |  136 +++++++++++++++++++++++++++++++++
 view/tax_rule_line_form.xml            |    4 +
 view/tax_rule_line_template_form.xml   |    4 +
 view/tax_rule_line_template_tree.xml   |    2 +
 view/tax_rule_line_tree.xml            |    2 +
 view/tax_rule_line_tree_sequence.xml   |    2 +
 13 files changed, 293 insertions(+), 37 deletions(-)

diffs (524 lines):

diff -r f9145f64fc84 -r c72071b493f7 CHANGELOG
--- a/CHANGELOG Mon May 06 14:59:51 2019 +0200
+++ b/CHANGELOG Sun Aug 18 19:03:12 2019 +0200
@@ -1,3 +1,5 @@
+* Add subdivision as criteria
+
 Version 5.2.0 - 2019-05-06
 * Bug fixes (see mercurial logs for details)
 
diff -r f9145f64fc84 -r c72071b493f7 __init__.py
--- a/__init__.py       Mon May 06 14:59:51 2019 +0200
+++ b/__init__.py       Sun Aug 18 19:03:12 2019 +0200
@@ -10,6 +10,7 @@
 
 def register():
     Pool.register(
+        account.TaxRule,
         account.TaxRuleLineTemplate,
         account.TaxRuleLine,
         module='account_tax_rule_country', type_='model')
diff -r f9145f64fc84 -r c72071b493f7 account.py
--- a/account.py        Mon May 06 14:59:51 2019 +0200
+++ b/account.py        Sun Aug 18 19:03:12 2019 +0200
@@ -3,16 +3,62 @@
 
 from trytond.pool import Pool, PoolMeta
 from trytond.model import fields
+from trytond.pyson import Eval
 
-__all__ = ['TaxRuleLineTemplate', 'TaxRuleLine', 'InvoiceLine']
+
+class TaxRule(metaclass=PoolMeta):
+    __name__ = 'account.tax.rule'
+
+    def apply(self, tax, pattern):
+        pool = Pool()
+        Subdivision = pool.get('country.subdivision')
+
+        def parents(subdivision):
+            while subdivision:
+                yield subdivision.id
+                subdivision = subdivision.parent
+
+        pattern = pattern.copy()
+        for name in ['from_subdivision', 'to_subdivision']:
+            subdivision = pattern.pop(name, None)
+            if not subdivision:
+                continue
+            subdivision = Subdivision(subdivision)
+            pattern[name] = list(parents(subdivision))
+        return super().apply(tax, pattern)
 
 
-class TaxRuleLineTemplate(metaclass=PoolMeta):
+class _TaxRuleLineMixin:
+    from_country = fields.Many2One(
+        'country.country', 'From Country', ondelete='RESTRICT',
+        help="Apply only to addresses of this country.")
+    from_subdivision = fields.Many2One(
+        'country.subdivision', "From Subdivision", ondelete='RESTRICT',
+        domain=[
+            ('country', '=', Eval('from_country', -1)),
+            ],
+        states={
+            'invisible': ~Eval('from_country'),
+            },
+        depends=['from_country'],
+        help="Apply only to addresses in this subdivision.")
+    to_country = fields.Many2One(
+        'country.country', 'To Country', ondelete='RESTRICT',
+        help="Apply only to addresses of this country.")
+    to_subdivision = fields.Many2One(
+        'country.subdivision', "To Subdivision", ondelete='RESTRICT',
+        domain=[
+            ('country', '=', Eval('to_country', -1)),
+            ],
+        states={
+            'invisible': ~Eval('to_country'),
+            },
+        depends=['to_country'],
+        help="Apply only to addresses in this subdivision.")
+
+
+class TaxRuleLineTemplate(_TaxRuleLineMixin, metaclass=PoolMeta):
     __name__ = 'account.tax.rule.line.template'
-    from_country = fields.Many2One('country.country', 'From Country',
-        ondelete='RESTRICT')
-    to_country = fields.Many2One('country.country', 'To Country',
-        ondelete='RESTRICT')
 
     def _get_tax_rule_line_value(self, rule_line=None):
         value = super(TaxRuleLineTemplate, self)._get_tax_rule_line_value(
@@ -20,18 +66,37 @@
         if not rule_line or rule_line.from_country != self.from_country:
             value['from_country'] = (
                 self.from_country.id if self.from_country else None)
+        if (not rule_line
+                or rule_line.from_subdivision != self.from_subdivision):
+            value['from_subdivision'] = (
+                self.from_subdivision.id if self.from_subdivision else None)
         if not rule_line or rule_line.to_country != self.to_country:
             value['to_country'] = (
                 self.to_country.id if self.to_country else None)
+        if (not rule_line
+                or rule_line.to_subdivision != self.to_subdivision):
+            value['to_subdivision'] = (
+                self.to_subdivision.id if self.to_subdivision else None)
         return value
 
 
-class TaxRuleLine(metaclass=PoolMeta):
+class TaxRuleLine(_TaxRuleLineMixin, metaclass=PoolMeta):
     __name__ = 'account.tax.rule.line'
-    from_country = fields.Many2One('country.country', 'From Country',
-        ondelete='RESTRICT')
-    to_country = fields.Many2One('country.country', 'To Country',
-        ondelete='RESTRICT')
+
+    def match(self, pattern):
+        for name in ['from_subdivision', 'to_subdivision']:
+            subdivision = getattr(self, name)
+            if name not in pattern:
+                if subdivision:
+                    return False
+                else:
+                    continue
+            pattern = pattern.copy()
+            subdivisions = pattern.pop(name)
+            if (subdivision is not None
+                    and subdivision.id not in subdivisions):
+                return False
+        return super().match(pattern)
 
 
 class InvoiceLine(metaclass=PoolMeta):
@@ -45,16 +110,28 @@
 
         pattern = super(InvoiceLine, self)._get_tax_rule_pattern()
 
-        from_country, to_country = None, None
+        from_country = from_subdivision = to_country = to_subdivision = None
         if isinstance(self.origin, SaleLine):
-            if self.origin.warehouse.address:
-                from_country = self.origin.warehouse.address.country
-            to_country = self.origin.sale.shipment_address.country
+            warehouse_address = self.origin.warehouse.address
+            if warehouse_address:
+                from_country = warehouse_address.country
+                from_subdivision = warehouse_address.subdivision
+            shipment_address = self.origin.sale.shipment_address
+            to_country = shipment_address.country
+            to_subdivision = shipment_address.subdivision
         elif isinstance(self.origin, PurchaseLine):
-            from_country = self.origin.purchase.invoice_address.country
-            if self.origin.purchase.warehouse.address:
-                to_country = self.origin.purchase.warehouse.address.country
+            invoice_address = self.origin.purchase.invoice_address
+            from_country = invoice_address.country
+            from_subdivision = invoice_address.subdivision
+            warehouse_address = self.origin.purchase.warehouse.address
+            if warehouse_address:
+                to_country = warehouse_address.country
+                to_subdivision = warehouse_address.subdivision
 
         pattern['from_country'] = from_country.id if from_country else None
+        pattern['from_subdivision'] = (
+            from_subdivision.id if from_subdivision else None)
         pattern['to_country'] = to_country.id if to_country else None
+        pattern['to_subdivision'] = (
+            to_subdivision.id if to_subdivision else None)
         return pattern
diff -r f9145f64fc84 -r c72071b493f7 doc/index.rst
--- a/doc/index.rst     Mon May 06 14:59:51 2019 +0200
+++ b/doc/index.rst     Sun Aug 18 19:03:12 2019 +0200
@@ -2,31 +2,33 @@
 ########################
 
 The account_tax_rule module extends the tax rule to add origin and destination
-countries as criteria.
+countries and subdivisions as criteria.
 
 Tax Rule Line
 *************
 
-Two criteria fields are added:
+Four criteria fields are added:
 
 - From Country: The country of origin
+- From Subdivision: The subdivision of origin
 - To Country: The country of destination
+- To Subdivision: The subdivision of destination
 
 The countries are picked from the origin document:
 
 - Sale:
 
-  - The origin country comes from the address of the warehouse.
-  - The destination country comes from the shipping address.
+  - The origin country and subdivision come from the address of the warehouse.
+  - The destination country and subdivision come from the shipping address.
 
 - Purchase:
 
-  - The origin country comes from the invoice address.
-  - The destination country comes from the address of the warehouse.
+  - The origin country and subdivision come from the invoice address.
+  - The destination country and subdivision come from the address of the 
warehouse.
 
 - Stock Consignment:
 
-  - The origin country comes from the warehouse's address of the location or
-    the delivery address for returned customer shipment.
-  - The destination country comes from the warehouse's address of the location
-    or the delivery address for customer shipment.
+  - The origin country and subdivision come from the warehouse's address of the
+    location or the delivery address for returned customer shipment.
+  - The destination country and subdivision come from the warehouse's address
+    of the location or the delivery address for customer shipment.
diff -r f9145f64fc84 -r c72071b493f7 purchase.py
--- a/purchase.py       Mon May 06 14:59:51 2019 +0200
+++ b/purchase.py       Sun Aug 18 19:03:12 2019 +0200
@@ -28,14 +28,20 @@
     def _get_tax_rule_pattern(self):
         pattern = super(PurchaseLine, self)._get_tax_rule_pattern()
 
-        from_country, to_country = None, None
+        from_country = from_subdivision = to_country = to_subdivision = None
         if self.purchase:
             if self.purchase.invoice_address:
                 from_country = self.purchase.invoice_address.country
+                from_subdivision = self.purchase.invoice_address.subdivision
             warehouse = self.purchase.warehouse
             if warehouse and warehouse.address:
                 to_country = warehouse.address.country
+                to_subdivision = warehouse.address.subdivision
 
         pattern['from_country'] = from_country.id if from_country else None
+        pattern['from_subdivision'] = (
+            from_subdivision.id if from_subdivision else None)
         pattern['to_country'] = to_country.id if to_country else None
+        pattern['to_subdivision'] = (
+            to_subdivision.id if to_subdivision else None)
         return pattern
diff -r f9145f64fc84 -r c72071b493f7 sale.py
--- a/sale.py   Mon May 06 14:59:51 2019 +0200
+++ b/sale.py   Sun Aug 18 19:03:12 2019 +0200
@@ -31,7 +31,7 @@
 
         pattern = super(SaleLine, self)._get_tax_rule_pattern()
 
-        from_country, to_country = None, None
+        from_country = from_subdivision = to_country = to_subdivision = None
         if self.id is None or self.id < 0:
             warehouse = self.get_warehouse('warehouse')
             if warehouse:
@@ -40,9 +40,15 @@
             warehouse = self.warehouse
         if warehouse and warehouse.address:
             from_country = warehouse.address.country
+            from_subdivision = warehouse.address.subdivision
         if self.sale and self.sale.shipment_address:
             to_country = self.sale.shipment_address.country
+            to_subdivision = self.sale.shipment_address.subdivision
 
         pattern['from_country'] = from_country.id if from_country else None
+        pattern['from_subdivision'] = (
+            from_subdivision.id if from_subdivision else None)
         pattern['to_country'] = to_country.id if to_country else None
+        pattern['to_subdivision'] = (
+            to_subdivision.id if to_subdivision else None)
         return pattern
diff -r f9145f64fc84 -r c72071b493f7 stock.py
--- a/stock.py  Mon May 06 14:59:51 2019 +0200
+++ b/stock.py  Sun Aug 18 19:03:12 2019 +0200
@@ -14,18 +14,30 @@
 
         pattern = super(Move, self)._get_tax_rule_pattern()
 
-        from_country, to_country = None, None
+        from_country = from_subdivision = to_country = to_subdivision = None
         if self.from_location.warehouse:
-            if self.from_location.warehouse.address:
-                from_country = self.from_location.warehouse.address.country
+            warehouse_address = self.from_location.warehouse.address
+            if warehouse_address:
+                from_country = warehouse_address.country
+                from_subdivision = warehouse_address.subdivision
         elif isinstance(self.origin, ShipmentOutReturn):
-            from_country = self.origin.delivery_address.country
+            delivery_address = self.origin.delivery_address
+            from_country = delivery_address.country
+            from_subdivision = delivery_address.subdivision
         if self.to_location.warehouse:
-            if self.to_location.warehouse.address:
-                to_country = self.to_location.warehouse.address.country
+            warehouse_address = self.to_location.warehouse.address
+            if warehouse_address:
+                to_country = warehouse_address.country
+                to_subdivision = warehouse_address.subdivision
         elif isinstance(self.origin, ShipmentOut):
-            to_country = self.origin.delivery_address.country
+            delivery_address = self.origin.delivery_address
+            to_country = delivery_address.country
+            to_subdivision = delivery_address.subdivision
 
         pattern['from_country'] = from_country.id if from_country else None
+        pattern['from_subdivision'] = (
+            from_subdivision.id if from_subdivision else None)
         pattern['to_country'] = to_country.id if to_country else None
+        pattern['to_subdivision'] = (
+            to_subdivision.id if to_subdivision else None)
         return pattern
diff -r f9145f64fc84 -r c72071b493f7 tests/test_account_tax_rule_country.py
--- a/tests/test_account_tax_rule_country.py    Mon May 06 14:59:51 2019 +0200
+++ b/tests/test_account_tax_rule_country.py    Sun Aug 18 19:03:12 2019 +0200
@@ -32,6 +32,142 @@
             update_chart.start.account = root
             update_chart.transition_update()
 
+    @classmethod
+    def _create_countries(cls):
+        pool = Pool()
+        Country = pool.get('country.country')
+        Subdivision = pool.get('country.subdivision')
+
+        country1 = Country(name="Country 1")
+        country1.save()
+        subdivision1 = Subdivision(
+            country=country1, name="Subdivision 1", code="SUB1",
+            type='province')
+        subdivision1.save()
+        subdivision11 = Subdivision(
+            country=country1, parent=subdivision1,
+            name="Sub-Subdivision 1", code="SUBSUB1", type='province')
+        subdivision11.save()
+        country2 = Country(name="Country 2")
+        country2.save()
+        subdivision2 = Subdivision(
+            country=country2, name="Subdivision 2", code="SUB2",
+            type='province')
+        subdivision2.save()
+
+        return [country1, country2]
+
+    def _get_taxes(cls):
+        pool = Pool()
+        Tax = pool.get('account.tax')
+        tax, = Tax.search([])
+        target_tax, = Tax.copy([tax])
+        return [tax, target_tax]
+
+    def _create_rule(cls, lines):
+        pool = Pool()
+        TaxRule = pool.get('account.tax.rule')
+        return TaxRule.create([{
+                    'name': 'Test',
+                    'kind': 'both',
+                    'lines': [('create', lines)],
+                    }])[0]
+
+    @with_transaction()
+    def test_tax_rule(self):
+        "Test tax rule"
+        country1, country2 = self._create_countries()[:2]
+        subdivision1 = country1.subdivisions[0]
+        subdivision2 = country2.subdivisions[0]
+        company = create_company()
+        with set_company(company):
+            create_chart(company, tax=True)
+            tax, target_tax = self._get_taxes()[:2]
+            tax_rule = self._create_rule([{
+                        'from_country': country1.id,
+                        'from_subdivision': subdivision1.id,
+                        'to_country': country2.id,
+                        'to_subdivision': subdivision2.id,
+                        'origin_tax': tax.id,
+                        'tax': target_tax.id,
+                        }])
+            pattern = {
+                'from_country': country1.id,
+                'from_subdivision': subdivision1.id,
+                'to_country': country2.id,
+                'to_subdivision': subdivision2.id,
+                }
+
+            self.assertListEqual(tax_rule.apply(tax, pattern), [target_tax.id])
+
+    @with_transaction()
+    def test_tax_rule_children(self):
+        "Test tax rule with children subdivision"
+        country = self._create_countries()[0]
+        parent_subdivision = [
+            s for s in country.subdivisions if not s.parent][0]
+        subdivision = [
+            s for s in country.subdivisions
+            if s.parent == parent_subdivision][0]
+        company = create_company()
+        with set_company(company):
+            create_chart(company, tax=True)
+            tax, target_tax = self._get_taxes()[:2]
+            tax_rule = self._create_rule([{
+                        'to_country': country.id,
+                        'to_subdivision': parent_subdivision.id,
+                        'origin_tax': tax.id,
+                        'tax': target_tax.id,
+                        }])
+            pattern = {
+                'to_country': country.id,
+                'to_subdivision': subdivision.id,
+                }
+
+            self.assertListEqual(tax_rule.apply(tax, pattern), [target_tax.id])
+
+    @with_transaction()
+    def test_tax_rule_no_subdivision(self):
+        "Test tax rule without subdivision"
+        country = self._create_countries()[0]
+        subdivision = country.subdivisions[0]
+        company = create_company()
+        with set_company(company):
+            create_chart(company, tax=True)
+            tax, target_tax = self._get_taxes()[:2]
+            tax_rule = self._create_rule([{
+                        'to_country': country.id,
+                        'origin_tax': tax.id,
+                        'tax': target_tax.id,
+                        }])
+            pattern = {
+                'to_country': country.id,
+                'to_subdivision': subdivision.id,
+                }
+
+            self.assertListEqual(tax_rule.apply(tax, pattern), [target_tax.id])
+
+    @with_transaction()
+    def test_tax_rule_no_subdivision_pattern(self):
+        "Test tax rule without subdivision in pattern"
+        country = self._create_countries()[0]
+        subdivision = country.subdivisions[0]
+        company = create_company()
+        with set_company(company):
+            create_chart(company, tax=True)
+            tax, target_tax = self._get_taxes()[:2]
+            tax_rule = self._create_rule([{
+                        'to_country': country.id,
+                        'to_subdivision': subdivision.id,
+                        'origin_tax': tax.id,
+                        'tax': target_tax.id,
+                        }])
+            pattern = {
+                'to_country': country.id,
+                }
+
+            self.assertListEqual(tax_rule.apply(tax, pattern), [tax.id])
+
 
 def suite():
     suite = trytond.tests.test_tryton.suite()
diff -r f9145f64fc84 -r c72071b493f7 view/tax_rule_line_form.xml
--- a/view/tax_rule_line_form.xml       Mon May 06 14:59:51 2019 +0200
+++ b/view/tax_rule_line_form.xml       Sun Aug 18 19:03:12 2019 +0200
@@ -5,7 +5,11 @@
     <xpath expr="/form/field[@name='origin_tax']" position="after">
         <label name="from_country"/>
         <field name="from_country"/>
+        <label name="from_subdivision"/>
+        <field name="from_subdivision"/>
         <label name="to_country"/>
         <field name="to_country"/>
+        <label name="to_subdivision"/>
+        <field name="to_subdivision"/>
     </xpath>
 </data>
diff -r f9145f64fc84 -r c72071b493f7 view/tax_rule_line_template_form.xml
--- a/view/tax_rule_line_template_form.xml      Mon May 06 14:59:51 2019 +0200
+++ b/view/tax_rule_line_template_form.xml      Sun Aug 18 19:03:12 2019 +0200
@@ -5,7 +5,11 @@
     <xpath expr="/form/field[@name='origin_tax']" position="after">
         <label name="from_country"/>
         <field name="from_country"/>
+        <label name="from_subdivision"/>
+        <field name="from_subdivision"/>
         <label name="to_country"/>
         <field name="to_country"/>
+        <label name="to_subdivision"/>
+        <field name="to_subdivision"/>
     </xpath>
 </data>
diff -r f9145f64fc84 -r c72071b493f7 view/tax_rule_line_template_tree.xml
--- a/view/tax_rule_line_template_tree.xml      Mon May 06 14:59:51 2019 +0200
+++ b/view/tax_rule_line_template_tree.xml      Sun Aug 18 19:03:12 2019 +0200
@@ -4,6 +4,8 @@
 <data>
     <xpath expr="/tree/field[@name='origin_tax']" position="after">
         <field name="from_country"/>
+        <field name="from_subdivision"/>
         <field name="to_country"/>
+        <field name="to_subdivision"/>
     </xpath>
 </data>
diff -r f9145f64fc84 -r c72071b493f7 view/tax_rule_line_tree.xml
--- a/view/tax_rule_line_tree.xml       Mon May 06 14:59:51 2019 +0200
+++ b/view/tax_rule_line_tree.xml       Sun Aug 18 19:03:12 2019 +0200
@@ -4,6 +4,8 @@
 <data>
     <xpath expr="/tree/field[@name='origin_tax']" position="after">
         <field name="from_country"/>
+        <field name="from_subdivision"/>
         <field name="to_country"/>
+        <field name="to_subdivision"/>
     </xpath>
 </data>
diff -r f9145f64fc84 -r c72071b493f7 view/tax_rule_line_tree_sequence.xml
--- a/view/tax_rule_line_tree_sequence.xml      Mon May 06 14:59:51 2019 +0200
+++ b/view/tax_rule_line_tree_sequence.xml      Sun Aug 18 19:03:12 2019 +0200
@@ -4,6 +4,8 @@
 <data>
     <xpath expr="/tree/field[@name='origin_tax']" position="after">
         <field name="from_country"/>
+        <field name="from_subdivision"/>
         <field name="to_country"/>
+        <field name="to_subdivision"/>
     </xpath>
 </data>

Reply via email to