Add subdivision as criteria
issue8252 review257851003
This commit is contained in:
1 parent
0eaad2504d
commit
44901cb047
13 files changed
+294
-38
No files matched your search
@@ -1,3 +1,5 @@
|
||||
* Add subdivision as criteria
|
||||
|
||||
Version 5.2.0 - 2019-05-06
|
||||
* Bug fixes (see mercurial logs for details)
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
@@ -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,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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user