Add subdivision as criteria

issue8252
review257851003
This commit is contained in:
Cédric Krier committed 2019-08-18 19:03:12 +02:00
1 parent 0eaad2504d
commit 44901cb047
13 files changed
+294 -38

No files matched your search

+2
View File
@@ -1,3 +1,5 @@
* Add subdivision as criteria
Version 5.2.0 - 2019-05-06
* Bug fixes (see mercurial logs for details)
+1
View File
@@ -10,6 +10,7 @@ from . import stock
def register():
Pool.register(
account.TaxRule,
account.TaxRuleLineTemplate,
account.TaxRuleLine,
module='account_tax_rule_country', type_='model')
+96 -19
View File
@@ -3,16 +3,62 @@
from trytond.pool import Pool, PoolMeta
from trytond.model import fields
__all__ = ['TaxRuleLineTemplate', 'TaxRuleLine', 'InvoiceLine']
from trytond.pyson import Eval
class TaxRuleLineTemplate(metaclass=PoolMeta):
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 _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 @@ class TaxRuleLineTemplate(metaclass=PoolMeta):
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 @@ class InvoiceLine(metaclass=PoolMeta):
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
+12 -10
View File
@@ -2,31 +2,33 @@ Account Tax Rule Country
########################
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.
+7 -1
View File
@@ -28,14 +28,20 @@ class PurchaseLine(metaclass=PoolMeta):
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
+7 -1
View File
@@ -31,7 +31,7 @@ class SaleLine(metaclass=PoolMeta):
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 @@ class SaleLine(metaclass=PoolMeta):
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
+19 -7
View File
@@ -14,18 +14,30 @@ class Move(metaclass=PoolMeta):
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
+136
View File
@@ -32,6 +32,142 @@ class AccountTaxRuleCountryTestCase(ModuleTestCase):
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()
+4
View File
@@ -5,7 +5,11 @@ this repository contains the full copyright notices and license terms. -->
<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>
+4
View File
@@ -5,7 +5,11 @@ this repository contains the full copyright notices and license terms. -->
<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>
+2
View File
@@ -4,6 +4,8 @@ this repository contains the full copyright notices and license terms. -->
<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>
+2
View File
@@ -4,6 +4,8 @@ this repository contains the full copyright notices and license terms. -->
<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>
+2
View File
@@ -4,6 +4,8 @@ this repository contains the full copyright notices and license terms. -->
<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>