diff --git a/__init__.py b/__init__.py
index 870fa16..c5f1a65 100644
--- a/__init__.py
+++ b/__init__.py
@@ -17,10 +17,13 @@ def register():
tax.Tax,
tax.TaxBoundary,
tax.TaxCode,
+ tax.TaxCodeLine,
+ tax.TaxLine,
tax.TaxRule,
module='account_us_sstp', type_='model')
Pool.register(
account.InvoiceLine,
+ account.InvoiceTax,
module='account_us_sstp', type_='model',
depends=['account_invoice'])
Pool.register(
diff --git a/account.py b/account.py
index 41db7c6..930783f 100644
--- a/account.py
+++ b/account.py
@@ -8,7 +8,7 @@ class InvoiceLine(metaclass=PoolMeta):
__name__ = 'account.invoice.line'
@fields.depends(
- '_parent_invoice.party', 'party', 'invoice', 'tax_date',
+ '_parent_invoice.party', 'party', 'invoice',
'_parent_invoice.accounting_date', '_parent_invoice.invoice_date',
'_parent_invoice.invoice_address')
def on_change_product(self):
@@ -16,12 +16,12 @@ def on_change_product(self):
Date = pool.get('ir.date')
Boundary = pool.get('account.tax.boundary')
- if self.invoice and self.invoice.tax_date:
- tax_date = self.invoice.tax_date
- elif self.tax_date:
+ if self.tax_date:
tax_date = self.tax_date
- elif self.taxes_date:
- tax_date = self.taxes_date
+ elif self.invoice and self.invoice.tax_date:
+ tax_date = self.invoice.tax_date
+ else:
+ tax_date = Date.today()
if self.invoice and self.invoice.invoice_address:
a = self.invoice.invoice_address
@@ -67,3 +67,61 @@ def on_change_product(self):
party.customer_tax_rule = boundary.rule
return super().on_change_product()
+
+class InvoiceTax(metaclass=PoolMeta):
+ __name__ = 'account.invoice.tax'
+
+ def get_move_lines(self):
+ lines = super().get_move_lines()
+
+ pool = Pool()
+ Date = pool.get('ir.date')
+ Boundary = pool.get('account.tax.boundary')
+
+ if self.invoice and self.invoice.tax_date:
+ tax_date = self.invoice.tax_date
+ else:
+ tax_date = Date.today()
+
+ if self.invoice and self.invoice.invoice_address:
+ a = self.invoice.invoice_address
+
+ pattern = r'(\d{5})-?(\d{4})?$'
+ match = re.match(pattern, a.postal_code)
+ if match:
+ zipcode, zipext = match.groups()
+
+ try:
+ boundary, = Boundary.search([
+ ('start_date', '<=', tax_date),
+ ['OR', [
+ ('end_date', '>=', tax_date)
+ ], [
+ ('end_date', '=', None)
+ ],
+ ],
+ ('authority.country', '=', a.country),
+ ('authority.subdivision', '=', a.subdivision),
+ ['OR', [
+ ('type', '=', '4'),
+ ('zipcode_low', '<=', zipcode),
+ ('zipcode_high', '>=', zipcode),
+ ('zipext_low', '<=', zipext),
+ ('zipext_high', '>=', zipext),
+ ], [
+ ('type', '=', 'Z'),
+ ('zipcode_low', '<=', zipcode),
+ ('zipcode_high', '>=', zipcode),
+ ],
+ ]
+ ], limit=1, order=[('type', 'DESC')])
+ except ValueError:
+ boundary = None
+
+ if boundary and boundary.code:
+ for line in lines:
+ for tax_line in line.tax_lines:
+ if tax_line.type == 'tax':
+ tax_line.code = boundary.code.code
+
+ return lines
diff --git a/sale.py b/sale.py
index 620ee04..85d37c1 100644
--- a/sale.py
+++ b/sale.py
@@ -48,11 +48,9 @@ def compute_taxes(self, party):
]
], limit=1, order=[('type', 'DESC')])
except ValueError:
- print("Could not find a matching tax rule for %s" % party)
boundary = None
if boundary and boundary.rule:
- print('using boundary rule %s' % boundary.rule.rec_name)
if party and not party.customer_tax_rule:
party.customer_tax_rule = boundary.rule
diff --git a/scripts/import_boundaries.py b/scripts/import_boundaries.py
index cd961a0..22fcf0d 100755
--- a/scripts/import_boundaries.py
+++ b/scripts/import_boundaries.py
@@ -4,6 +4,7 @@
from __future__ import print_function
import csv
+from collections import defaultdict
import datetime as dt
import os
import sys
@@ -19,7 +20,7 @@
import zipfile
from argparse import ArgumentParser
from io import BytesIO, TextIOWrapper
-#from itertools import batched
+from itertools import batched
try:
from progressbar import ETA, Bar, ProgressBar, SimpleProgress
@@ -122,57 +123,56 @@ def fetch(code):
def get_places(code):
Place = Model.get('census.place')
return {p.code_fips: p for p in Place.find([
- ('subdivision.code', '=', 'US-%s' % code)
+ ('subdivision.code', '=', code)
])}
-def get_tax_codes(code):
- return {c.code_ser: c}
+class TaxRuleCollector:
-def import_boundaries(code, boundaries):
- sys.stderr.write('Importing boundaries')
- sys.stderr.flush()
- Boundary = Model.get('account.tax.boundary')
- Tax = Model.get('account.tax')
- TaxRule = Model.get('account.tax.rule')
- TaxRuleLine = Model.get('account.tax.rule')
+ def __init__(self, code, places):
+ self.places = places
+ self.rules = {}
+ self.tax_sets = {}
+ self.generic_taxes = {}
- places = get_places(code)
+ self.Tax = Model.get('account.tax')
+ self.TaxRule = Model.get('account.tax.rule')
+ self.TaxRuleLine = Model.get('account.tax.rule')
- def get_rule(jurisdiction, authority):
+ def get_rule(self, jurisdiction, authority):
code_fips = jurisdiction.code_fips
- rule = rules.get(code_fips)
+ rule = self.rules.get(code_fips)
if not rule:
try:
- rule, = TaxRule.find([
+ rule, = self.TaxRule.find([
+ ('authority', '=', authority),
('jurisdiction.code_fips', '=', code_fips),
])
except ValueError:
return
- rules[code_fips] = rule
+ self.rules[code_fips] = rule
return rule
- rules = {}
- def get_taxes(jurisdiction, authority):
+ def get_taxes(self, jurisdiction, authority):
code_fips = jurisdiction.code_fips
- taxes = tax_sets.get(code_fips)
+ taxes = self.tax_sets.get(code_fips)
if not taxes:
try:
- taxes = Tax.find([
- ('jurisdiction.code_fips', '=', code_fips),
+ taxes = self.Tax.find([
('authority', '=', authority),
+ ('jurisdiction.code_fips', '=', code_fips),
('type', '=', 'none'),
+ ('parent', '=', None),
])
except ValueError:
return []
- tax_sets[code_fips] = taxes
+ self.tax_sets[code_fips] = taxes
return taxes
- tax_sets = {}
- def get_generic_tax(tax):
- generic_tax = generic_taxes.get(tax.id)
+ def get_generic_tax(self, tax):
+ generic_tax = self.generic_taxes.get(tax.id)
if not generic_tax:
try:
- generic_tax, = Tax.find([
+ generic_tax, = self.Tax.find([
('authority', '=', None),
('group', '=', tax.group),
('company', '=', tax.company),
@@ -183,54 +183,205 @@ def get_generic_tax(tax):
])
except ValueError:
sys.exit("Error could not find generic tax for %s" % tax.name)
- generic_taxes[tax.id] = generic_tax
+ self.generic_taxes[tax.id] = generic_tax
return generic_tax
- generic_taxes = {}
- f = TextIOWrapper(BytesIO(boundaries), encoding='utf-8')
- reader = csv.DictReader(f, fieldnames=_fieldnames,
- restkey='special_districts')
- records = []
- for row in _progress(reader):
- jurisdiction = places.get(row['fips_place_code'],
- places[row['fips_county_code']])
- authority = places[row['fips_state_code']]
- start_date = dt.datetime.strptime(row['start_date'], '%Y%m%d').date()
- end_date = dt.datetime.strptime(row['end_date'], '%Y%m%d').date()
- end_date = None if end_date == dt.date.max else end_date
+ def collect(self, row):
+ jurisdiction = self.places.get(row['fips_place_code'],
+ self.places[row['fips_county_code']])
+ authority = self.places[row['fips_state_code']]
- if end_date != None:
- continue #TODO: for now only load current records
+ rule = self.get_rule(jurisdiction, authority)
- rule = get_rule(jurisdiction, authority)
if not rule:
name = '%s, %s Retail' % (jurisdiction.name,
jurisdiction.subdivision.code)
- rule = TaxRule(
+ rule = self.TaxRule(
name=name,
jurisdiction=jurisdiction,
authority=authority)
+
for code_fips in ['fips_state_indicator', 'fips_county_code', 'fips_place_code']:
if all(c == '0' for c in row[code_fips]):
continue
- place = places.get(row[code_fips])
+ place = self.places.get(row[code_fips])
if place:
- taxes = get_taxes(place, authority)
- for tax in taxes:
+ for tax in self.get_taxes(place, authority):
+ origin_tax = self.get_generic_tax(tax)
+
line = rule.lines.new()
- line.start_date = start_date
- line.end_date = end_date
line.group = tax.group
+ line.origin_tax = origin_tax
line.tax = tax
- line.origin_tax = get_generic_tax(tax)
line.to_country = line.from_country = place.country
line.to_subdivision = place.subdivision
if tax.sourcing == 'intrastate':
line.from_subdivision = place.subdivision
else:
line.from_subdivision = None
+
+ for sd in batched(row['special_districts'], n=3):
+ pass
+
rule.save()
+ return rule
+
+class TaxCodeCollector:
+
+ def __init__(self, code, places):
+ self.tax_codes = {}
+ self.places = places
+
+ authority = None
+ try:
+ authority, = [v for v in places.values() if v.parent == None]
+ except:
+ sys.exit("\nError could not find a state authority for the code: %s" % code)
+ self.authority = authority
+
+ with open(os.path.join(os.path.dirname(__file__),
+ 'jurisdictions.csv'), newline='') as csvfile:
+ reader = csv.DictReader(csvfile, fieldnames=['code', 'code_tax', 'name'])
+ self.names = {r['code_tax']: r['name'] for r in reader if r['code'] == code}
+
+ TaxCode = Model.get('account.tax.code')
+ root = TaxCode(name="%s Streamlined Sales Tax Report" % self.authority.subdivision.name,
+ code='SSTR-%s' % self.authority.subdivision.code,
+ authority=self.authority)
+ root.save()
+
+ taxable_sales = root.childs.new()
+ taxable_sales.name = "Taxable Sales"
+ taxable_sales.code = 'A'
+ taxable_sales.authority = self.authority
+ taxable_sales.save()
+
+ total_sales = taxable_sales.childs.new()
+ total_sales.name = "Total Sales"
+ total_sales.code = '1'
+ total_sales.authority = self.authority
+ total_sales.save()
+
+ exemptions = taxable_sales.childs.new()
+ exemptions.name = "Exemptions and Deductions"
+ exemptions.code = '2'
+ exemptions.authority = self.authority
+ exemptions.save()
+
+ for name in ['Agriculture', 'Direct Pay', 'Government Exemption Organizations',
+ 'Manufacturing', 'Resale', 'Other']:
+ subcode = exemptions.childs.new()
+ subcode.name = name
+ subcode.authority = self.authority
+ subcode.save()
+
+ total_tax = root.childs.new()
+ total_tax.name = "Total Tax Due"
+ total_tax.code = 'B'
+ total_tax.authority = self.authority
+ total_tax.save()
+
+ self.total_sales = total_sales
+ self.total_tax = total_tax
+
+
+ def get_tax_code(self, code_tax):
+ tax_code = self.tax_codes.get(code_tax)
+ if not tax_code:
+ TaxCode = Model.get('account.tax.code')
+ try:
+ tax_code, = TaxCode.find([
+ ('authority', '=', self.authority),
+ ('code', '=', code_tax),
+ ])
+ except ValueError:
+ return
+ self.tax_codes[code_tax] = tax_code
+ return tax_code
+
+ def collect(self, row):
+ code_tax = row['composite_ser_code']
+ if not code_tax or all(c == '0' for c in code_tax):
+ return
+
+ tax_code = self.get_tax_code(code_tax)
+ if not tax_code:
+ name = self.names.get(code_tax)
+ if not name:
+ print('Could not find jurisdiction name for %s' % code_tax)
+ name = code_tax
+
+ tax_code = self.total_tax.childs.new()
+ tax_code.name = name
+ tax_code.code = code_tax
+ tax_code.authority = self.authority
+
+ tax_code.save()
+ return tax_code
+
+def import_(code, boundaries):
+ sys.stderr.write('Importing')
+ sys.stderr.flush()
+ Boundary = Model.get('account.tax.boundary')
+
+ places = get_places(code)
+ code_collector = TaxCodeCollector(code, places)
+ rule_collector = TaxRuleCollector(code, places)
+
+ _seen = defaultdict(set)
+ def seen(rule, code=None):
+ if code:
+ if _seen.get(code) and rule in _seen[code]:
+ return True
+ _seen[code].add(rule)
+ return False
+ else:
+ if _seen.get(rule):
+ return True
+ _seen[rule].add(1)
+ return False
+
+ _taxes = set()
+ def setup_tax_lines(code, tax, amount='tax'):
+ for op, type_ in zip(['+', '-'], ['invoice', 'credit']):
+ line = code.lines.new()
+ line.operator = op
+ line.tax = tax
+ line.amount = amount
+ line.type = type_
+
+ f = TextIOWrapper(BytesIO(boundaries), encoding='utf-8')
+ reader = csv.DictReader(f, fieldnames=_fieldnames,
+ restkey='special_districts')
+ records = []
+ for row in _progress(reader):
+ authority = places[row['fips_state_code']]
+ start_date = dt.datetime.strptime(row['start_date'], '%Y%m%d').date()
+ end_date = dt.datetime.strptime(row['end_date'], '%Y%m%d').date()
+ end_date = None if end_date == dt.date.max else end_date
+
+ tax_code = code_collector.collect(row)
+ rule = rule_collector.collect(row)
+
+ if tax_code and not seen(rule, code=tax_code):
+ for line in rule.lines:
+ for tax in line.tax.childs:
+ taxes = [line.tax for line in tax_code.lines]
+ if tax not in taxes:
+ setup_tax_lines(tax_code, tax)
+ _taxes.add(tax)
+ tax_code.save()
+ elif not tax_code and not seen(rule):
+ for line in rule.lines:
+ for tax in line.tax.childs:
+ total_tax = code_collector.total_tax
+ taxes = [line.tax for line in total_tax.lines]
+ if not tax in taxes:
+ setup_tax_lines(total_tax, tax)
+ _taxes.add(tax)
+ total_tax.save()
+
records.append(Boundary(
type=row['record_type'],
@@ -242,32 +393,23 @@ def get_generic_tax(tax):
zipcode_high=row['zipcode_high'],
zipext_high=row['zipext_high'],
rule=rule,
+ code=tax_code,
))
if reader.line_num % 10000 == 0:
Boundary.save(records)
records = []
- #for sd in batched(row['special_districts'], n=3):
- # pass
-
Boundary.save(records)
- print('.', file=sys.stderr)
-def update_tax_codes(code):
- pass
+ total_sales = code_collector.total_sales
+ for tax in _taxes:
+ # only the state-level bases are needed
+ if tax.jurisdiction == code_collector.authority:
+ setup_tax_lines(total_sales, tax, amount='base')
+ total_sales.save()
-def get_tax_rules(code):
- TaxRule = Model.get('account.tax.rule')
- return {(r.county_fips, r.place_fips): r for r in TaxRule.find([
- ('authority.subdivision.code', '=', 'US-%s' % code)
- ])}
-
-def update_tax_rules(code):
- TaxRule = Model.get('account.tax.rule')
-
-def update_tax_rules_interstate(codes):
- pass
+ print('.', file=sys.stderr)
_fieldnames = ['record_type', 'start_date', 'end_date',
'address_range_low', 'address_range_high', 'odd_even_indicator', 'street_predirectional',
@@ -277,12 +419,6 @@ def update_tax_rules_interstate(codes):
'composite_ser_code', 'fips_state_code', 'fips_state_indicator','fips_county_code',
'fips_place_code', 'fips_place_class_code', 'longitude', 'latitude']
-#_fieldnames.extend([k + str(i) for i in range(1, 21) for k in [
-# 'special_tax_district_code_source_',
-# 'special_tax_district_code_',
-# 'special_tax_district_authority_'
-# ]])
-
def main(database, codes, config_file=None):
config.set_trytond(database, config_file=config_file)
do_import(codes)
@@ -294,9 +430,8 @@ def do_import(codes):
code = code.upper()
clean_boundaries('US-%s' % code)
clean_tax_rules('US-%s' % code)
- #clean_tax_codes('US-%s' % code)
- boundaries = fetch(code)
- import_boundaries(code, boundaries)
+ clean_tax_codes('US-%s' % code)
+ import_('US-%s' % code, fetch(code))
def run():
diff --git a/scripts/jurisdictions.csv b/scripts/jurisdictions.csv
new file mode 100644
index 0000000..446913e
--- /dev/null
+++ b/scripts/jurisdictions.csv
@@ -0,0 +1,336 @@
+US-UT,01000,Beaver County
+US-UT,01002,Beaver City
+US-UT,01008,Milford
+US-UT,01009,Minersville
+US-UT,01500,UIPA Min Mt - Beaver Co
+US-UT,01501,UIPA Min Mt - Beaver City
+US-UT,01502,UIPA Min Mt - Milford
+US-UT,02000,Box Elder County
+US-UT,02004,Bear River
+US-UT,02017,Brigham
+US-UT,02025,Corinne
+US-UT,02032,Deweyville
+US-UT,02035,Elwood
+US-UT,02041,Fielding
+US-UT,02044,Garland
+US-UT,02054,Honeyville
+US-UT,02057,Howell
+US-UT,02069,Mantua
+US-UT,02086,Perry
+US-UT,02090,Plymouth
+US-UT,02092,Portage
+US-UT,02100,Snowville
+US-UT,02113,Tremonton
+US-UT,02120,Willard
+US-UT,02500,UIPA GS - Box Elder Co
+US-UT,02501,UIPA GS - Brigham City
+US-UT,02502,UIPA GS - Garland
+US-UT,02503,UIPA GS - Tremonton
+US-UT,03000,Cache County
+US-UT,03001,Amalga
+US-UT,03014,Clarkston
+US-UT,03017,Cornish
+US-UT,03032,Hyde Park
+US-UT,03033,Hyrum
+US-UT,03036,Lewiston
+US-UT,03038,Logan
+US-UT,03041,Mendon
+US-UT,03044,Millville
+US-UT,03047,Newton
+US-UT,03049,North Logan
+US-UT,03053,Paradise
+US-UT,03056,Providence
+US-UT,03059,Richmond
+US-UT,03060,River Heights
+US-UT,03062,Smithfield
+US-UT,03076,Wellsville
+US-UT,03081,Trenton
+US-UT,03098,Nibley
+US-UT,03900,Cache Valley Transit
+US-UT,04000,Carbon County
+US-UT,04016,Helper
+US-UT,04035,Price
+US-UT,04040,Scofield
+US-UT,04053,Wellington
+US-UT,04058,East Carbon
+US-UT,05000,Daggett County
+US-UT,05002,Dutch John
+US-UT,05006,Manila
+US-UT,06000,Davis County
+US-UT,06004,Bountiful
+US-UT,06006,Centerville
+US-UT,06008,Clearfield
+US-UT,06010,Fruit Heights
+US-UT,06017,Farmington
+US-UT,06026,Kaysville
+US-UT,06030,Layton
+US-UT,06035,North Salt Lake
+US-UT,06045,South Weber
+US-UT,06048,Sunset
+US-UT,06049,Syracuse
+US-UT,06056,West Point
+US-UT,06057,Woods Cross
+US-UT,06059,Clinton
+US-UT,06061,West Bountiful
+US-UT,06300,Falcon Hill Davis
+US-UT,06301,Falcon Hill Clearfield
+US-UT,06302,Falcon Hill Sunset
+US-UT,07000,Duchesne County
+US-UT,07001,Altamont
+US-UT,07008,Duchesne City
+US-UT,07017,Myton
+US-UT,07019,Roosevelt
+US-UT,07020,Tabiona
+US-UT,08000,Emery County
+US-UT,08001,Castle Dale
+US-UT,08003,Clawson
+US-UT,08004,Cleveland
+US-UT,08007,Elmo
+US-UT,08008,Emery City
+US-UT,08009,Ferron
+US-UT,08011,Green River
+US-UT,08012,Huntington
+US-UT,08016,Orangeville
+US-UT,09000,Garfield County
+US-UT,09001,Antimony
+US-UT,09002,Boulder
+US-UT,09003,Bryce Canyon
+US-UT,09004,Cannonville
+US-UT,09005,Escalante
+US-UT,09006,Hatch
+US-UT,09008,Henrieville
+US-UT,09011,Panguitch
+US-UT,09015,Tropic
+US-UT,10000,Grand County
+US-UT,10005,Castle Valley
+US-UT,10011,Moab
+US-UT,11000,Iron County
+US-UT,11003,Cedar City
+US-UT,11005,Enoch
+US-UT,11012,Kanarraville
+US-UT,11018,Paragonah
+US-UT,11019,Parowan
+US-UT,11028,Brian Head
+US-UT,11501,Inland Port Iron Springs
+US-UT,12000,Juab County
+US-UT,12009,Eureka
+US-UT,12019,Levan
+US-UT,12024,Mona
+US-UT,12026,Nephi
+US-UT,12030,Rocky Ridge Town
+US-UT,12050,Santaquin South
+US-UT,12500,UIPA Agri-Park
+US-UT,13000,Kane County
+US-UT,13001,Alton
+US-UT,13002,Glendale
+US-UT,13004,Kanab
+US-UT,13007,Orderville
+US-UT,13010,Big Water
+US-UT,14000,Millard County
+US-UT,14010,Delta
+US-UT,14014,Fillmore
+US-UT,14023,Hinckley
+US-UT,14024,Holden
+US-UT,14026,Kanosh
+US-UT,14028,Leamington
+US-UT,14030,Lynndyl
+US-UT,14034,Meadow
+US-UT,14037,Oak City
+US-UT,14040,Scipio
+US-UT,15000,Morgan County
+US-UT,15007,Morgan City
+US-UT,16000,Piute County
+US-UT,16003,Circleville
+US-UT,16005,Junction
+US-UT,16006,Kingston
+US-UT,16007,Marysvale
+US-UT,17000,Rich County
+US-UT,17001,Garden City
+US-UT,17002,Laketown
+US-UT,17005,Randolph
+US-UT,17010,Woodruff
+US-UT,18000,Salt Lake County
+US-UT,18003,Alta
+US-UT,18010,Brighton
+US-UT,18019,Bluffdale
+US-UT,18020,Cottonwood Heights
+US-UT,18039,Draper
+US-UT,18060,Herriman
+US-UT,18065,Holladay
+US-UT,18093,Midvale
+US-UT,18094,Millcreek
+US-UT,18096,Murray
+US-UT,18118,Riverton
+US-UT,18122,Salt Lake City
+US-UT,18131,Sandy
+US-UT,18138,South Jordan
+US-UT,18139,South Salt Lake
+US-UT,18142,Taylorsville
+US-UT,18155,West Jordan
+US-UT,18167,West Valley City
+US-UT,18300,Utah Data Center SL Co
+US-UT,18301,MIDA Sundance - SLC
+US-UT,18401,Copperton Township
+US-UT,18402,Emigration Canyon Township
+US-UT,18403,Kearns Township
+US-UT,18404,Magna Township
+US-UT,18405,White City Township
+US-UT,18501,Inland Port Salt Lake City
+US-UT,18502,Inland Port West Valley City
+US-UT,18503,Inland Port Magna
+US-UT,18504,Inland Port Salt Lake County
+US-UT,18601,SLC Convention Hotel
+US-UT,18701,Salt Lake City HTRZ
+US-UT,18702,Sandy HTRZ
+US-UT,18703,South Salt Lake HTRZ
+US-UT,19000,San Juan County
+US-UT,19002,Blanding
+US-UT,19004,Bluff
+US-UT,19009,Monticello
+US-UT,20000,Sanpete County
+US-UT,20004,Centerfield
+US-UT,20008,Ephraim
+US-UT,20009,Fairview
+US-UT,20010,Fayette
+US-UT,20011,Fountain Green
+US-UT,20014,Gunnison
+US-UT,20020,Manti
+US-UT,20021,Mayfield
+US-UT,20023,Moroni
+US-UT,20024,Mt. Pleasant
+US-UT,20031,Spring City
+US-UT,20032,Sterling
+US-UT,20033,Wales
+US-UT,21000,Sevier County
+US-UT,21001,Annabella
+US-UT,21002,Aurora
+US-UT,21007,Central Valley
+US-UT,21014,Elsinore
+US-UT,21018,Glenwood
+US-UT,21025,Joseph
+US-UT,21029,Koosharem
+US-UT,21031,Monroe
+US-UT,21033,Redmond
+US-UT,21034,Richfield
+US-UT,21035,Salina
+US-UT,21038,Sigurd
+US-UT,22000,Summit County
+US-UT,22006,Coalville
+US-UT,22013,Francis
+US-UT,22017,Henefer
+US-UT,22022,Kamas
+US-UT,22029,Oakley
+US-UT,22030,Park City
+US-UT,22900,Snyderville Basin Tr Dist
+US-UT,23000,Tooele County
+US-UT,23018,Erda
+US-UT,23020,Erda City West
+US-UT,23023,Grantsville
+US-UT,23031,Lakepoint City
+US-UT,23035,Lakepoint Transit
+US-UT,23046,Stockton
+US-UT,23048,Tooele City
+US-UT,23050,Vernon
+US-UT,23052,Wendover
+US-UT,23056,Rush Valley
+US-UT,23065,Lincoln
+US-UT,23066,Stansbury Park
+US-UT,23301,Dugway Proving Grounds
+US-UT,23500,UIPA Tooele Valley
+US-UT,23501,UIPA Twenty Wells
+US-UT,24000,Uintah County
+US-UT,24014,Naples
+US-UT,24024,Vernal
+US-UT,24028,Ballard
+US-UT,25000,Utah County
+US-UT,25001,Alpine
+US-UT,25002,American Fork
+US-UT,25010,Bluffdale South
+US-UT,25019,Cedar Fort
+US-UT,25029,Draper City South
+US-UT,25030,Eagle Mountain
+US-UT,25035,Fairfield
+US-UT,25038,Genola
+US-UT,25043,Goshen
+US-UT,25066,Lehi
+US-UT,25070,Lindon
+US-UT,25073,Mapleton
+US-UT,25083,Orem
+US-UT,25085,Payson
+US-UT,25088,Pleasant Grove
+US-UT,25090,Provo
+US-UT,25096,Salem
+US-UT,25097,Santaquin
+US-UT,25098,Saratoga Springs
+US-UT,25099,Highland
+US-UT,25103,Spanish Fork
+US-UT,25106,Springville
+US-UT,25117,Vineyard
+US-UT,25123,Cedar Hills
+US-UT,25124,Elk Ridge
+US-UT,25125,Woodland Hills
+US-UT,25300,Utah Data Center Utah Co
+US-UT,25301,MIDA Sundance - Ut Co
+US-UT,25500,UIPA Verk - Ut Co
+US-UT,25501,UIPA Verk - Spanish ForkĀ
+US-UT,25702,Vineyard HTRZ
+US-UT,25800,ULA Utah County
+US-UT,25801,ULA Genola
+US-UT,25802,ULA Lehi
+US-UT,25803,ULA Lindon
+US-UT,25804,ULA Provo
+US-UT,25805,ULA Vineyard
+US-UT,26000,Wasatch County
+US-UT,26003,Charleston
+US-UT,26005,Daniel
+US-UT,26008,Heber
+US-UT,26009,Independence
+US-UT,26010,Interlaken
+US-UT,26011,Midway
+US-UT,26013,Park City East
+US-UT,26014,Wallsburg
+US-UT,26020,Hideout
+US-UT,26300,Military Recreation - Wasatch
+US-UT,26301,Military Recreation - Hideout
+US-UT,26302,Military Recreation - MWR Hotel
+US-UT,26303,Military Recreation - GAEC PID
+US-UT,27000,Washington County
+US-UT,27002,Apple Valley
+US-UT,27005,Enterprise
+US-UT,27008,Hurricane
+US-UT,27010,Ivins
+US-UT,27011,La Verkin
+US-UT,27012,Leeds
+US-UT,27015,New Harmony
+US-UT,27019,Rockville
+US-UT,27020,St George
+US-UT,27021,Santa Clara
+US-UT,27023,Springdale
+US-UT,27024,Toquerville
+US-UT,27026,Virgin
+US-UT,27027,Washington City
+US-UT,27035,Hildale
+US-UT,28000,Wayne County
+US-UT,28001,Bicknell
+US-UT,28005,Hanksville
+US-UT,28007,Loa
+US-UT,28008,Lyman
+US-UT,28010,Torrey
+US-UT,29000,Weber County
+US-UT,29012,Farr West
+US-UT,29016,Harrisville
+US-UT,29018,Hooper
+US-UT,29019,Huntsville
+US-UT,29022,Marriott-Slaterville
+US-UT,29026,North Ogden
+US-UT,29027,Ogden
+US-UT,29030,Plain City
+US-UT,29031,Pleasant View
+US-UT,29036,Riverdale
+US-UT,29037,Roy
+US-UT,29040,South Ogden
+US-UT,29043,Uintah
+US-UT,29049,Washington Terrace
+US-UT,29051,West Haven
+US-UT,29300,Falcon Hill Riverdale
+US-UT,29301,Falcon Hill Roy
diff --git a/tax.py b/tax.py
index 3ceb6e8..8978106 100644
--- a/tax.py
+++ b/tax.py
@@ -1,6 +1,5 @@
from trytond.model import (
- DeactivableMixin, MatchMixin, ModelSQL, ModelView, fields,
- sequence_ordered, tree)
+ MatchMixin, ModelSQL, ModelView, fields)
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval
from trytond.transaction import Transaction
@@ -44,6 +43,22 @@ def copy(cls, taxes, default=None):
default.setdefault('authority', None)
return super().copy(taxes, default=default)
+ @classmethod
+ def _amount_where(cls, tax_line, move_line, move):
+ where = super()._amount_where(tax_line, move_line, move)
+
+ context = Transaction().context
+ code_id = context.get('code')
+ amount = context.get('amount')
+
+ if code_id and amount == 'tax':
+ TaxCode = Pool().get('account.tax.code')
+ code = TaxCode(code_id)
+ return where & (tax_line.code == code.code)
+ else:
+ return where
+
+
class TaxBoundary(ModelView, ModelSQL, MatchMixin):
"Tax Boundary"
__name__ = 'account.tax.boundary'
@@ -51,18 +66,33 @@ class TaxBoundary(ModelView, ModelSQL, MatchMixin):
('A', 'Address'),
('Z', 'ZIP Code'),
('4', 'ZIP+4 Code'),
- ], "Boundary Type")
- start_date = fields.Date("Starting Date")
+ ], "Boundary Type", required=True)
+ start_date = fields.Date("Starting Date", required=True)
end_date = fields.Date("End Date")
- zipcode_low = fields.Char("ZIP Code Low", size=5)
- zipcode_high = fields.Char("ZIP Code High", size=5)
- zipext_low = fields.Char("ZIP+4 Code Low", size=4)
- zipext_high = fields.Char("ZIP+4 Code High", size=4)
+ zipcode_low = fields.Char("ZIP Code Low", size=5, states={
+ 'required': Eval('type').in_(['Z', '4']),
+ })
+ zipcode_high = fields.Char("ZIP Code High", size=5, states={
+ 'required': Eval('type').in_(['Z', '4']),
+ })
+ zipext_low = fields.Char("ZIP+4 Code Low", size=4, states={
+ 'required': Eval('type') == '4',
+ })
+ zipext_high = fields.Char("ZIP+4 Code High", size=4, states={
+ 'required': Eval('type') == '4',
+ })
authority = fields.Many2One('census.place', "Authority",
- domain=[('parent', '=', None)],
+ domain=[('parent', '=', None)], required=True,
help="The entity that administers this tax boundary")
rule = fields.Many2One('account.tax.rule', "Tax Rule",
- domain=[('authority', '=', Eval('authority', -1))],
+ domain=[
+ ('authority', '=', Eval('authority', -1))
+ ],
+ ondelete='RESTRICT', required=True)
+ code = fields.Many2One('account.tax.code', "Tax Code",
+ domain=[
+ ('authority', '=', Eval('authority', -1))
+ ],
ondelete='RESTRICT')
class TaxCode(metaclass=PoolMeta):
@@ -72,6 +102,35 @@ class TaxCode(metaclass=PoolMeta):
domain=[('parent', '=', None)],
help="The entity that administers this tax code")
+
+class TaxCodeLine(metaclass=PoolMeta):
+ "Tax Code Line"
+ __name__ = 'account.tax.code.line'
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls.tax.context['code'] = Eval('code')
+ cls.tax.depends.add('code')
+ cls.tax.context['amount'] = Eval('amount')
+ cls.tax.depends.add('amount')
+ cls.code.ondelete = 'CASCADE'
+
+ @property
+ def _line_domain(self):
+ domain = super()._line_domain
+ domain.append(['OR',
+ [('code', '=', self.code.code)],
+ [('type', '=', 'base')],
+ ])
+ return domain
+
+
+class TaxLine(metaclass=PoolMeta):
+ "Tax Line"
+ __name__ = 'account.tax.line'
+ code = fields.Char("Reporting Code")
+
class TaxRule(metaclass=PoolMeta):
__name__ = 'account.tax.rule'
diff --git a/tax.xml b/tax.xml
index 01682c3..944d1ab 100644
--- a/tax.xml
+++ b/tax.xml
@@ -36,5 +36,16 @@
tax_code_list
+
+ account.tax.line
+
+ tax_line_form
+
+
+ account.tax.line
+
+ tax_line_tree
+
+
diff --git a/view/tax_line_form.xml b/view/tax_line_form.xml
new file mode 100644
index 0000000..9dc906d
--- /dev/null
+++ b/view/tax_line_form.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/view/tax_line_tree.xml b/view/tax_line_tree.xml
new file mode 100644
index 0000000..088cdc9
--- /dev/null
+++ b/view/tax_line_tree.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+