pyaccuwage/record.py
Binh Van Nguyen 0646bf7b9b Added record validation functions for everything (that we saw in the PDF),
we should go over them once more to make sure we didn't miss anything, but
testing validation should probably be done after that. Verify that the
record ordering enforcement code is correct, then start thinking of how
to get data from external sources into the record generator.
2011-06-11 14:45:12 -05:00

348 lines
19 KiB
Python

import model
from fields import *
import enums
__all__ = RECORD_TYPES = ['SubmitterRecord', 'EmployerRecord',
'EmployeeWageRecord', 'OptionalEmployeeWageRecord',
'TotalRecord', 'OptionalTotalRecord',
'StateTotalRecord', 'FinalRecord',]
class SubmitterRecord(model.Model):
record_identifier = 'RA'
required = True
submitter_ein = NumericField(max_length=9)
user_id = TextField(max_length=8)
software_vendor = TextField(max_length=4, required=False)
blank1 = BlankField(max_length=5)
resub_indictator = BooleanField(required=False)
resub_identifier = TextField(max_length=6, required=False)
software_code = StaticField(value='98') # In-house program
company_name = TextField(max_length=57)
company_address = TextField(max_length=22)
company_delivery_address = TextField(max_length=22)
company_city = TextField(max_length=22)
company_state = StateField()
company_zipcode = TextField(max_length=5)
company_zipcode_ext = TextField(max_length=4, required=False)
blank2 = BlankField(max_length=5)
company_foreign_state_province= TextField(max_length=23, required=False)
company_foreign_postal_code = TextField(max_length=15, required=False)
company_country_code = TextField(max_length=2, required=False)
submitter_name = TextField(max_length=57)
submitter_address = TextField(max_length=22)
submitter_delivery_address = TextField(max_length=22)
submitter_city = TextField(max_length=22)
submitter_state = StateField()
submitter_zipcode = TextField(max_length=5)
submitter_zipcode_ext = TextField(max_length=4, required=False)
blank3 = BlankField(max_length=5)
submitter_foreign_state_province = TextField(max_length=23, required=False)
submitter_foreign_postal_code = TextField(max_length=15, required=False)
submitter_country_code = TextField(max_length=2, required=False)
contact_name = TextField(max_length=27)
contact_phone = TextField(max_length=15)
contact_phone_ext = TextField(max_length=5, required=False)
blank4 = BlankField(max_length=3)
contact_email = EmailField(max_length=40)
blank5 = BlankField(max_length=3)
contact_fax = TextField(max_length=10, required=False)
preferred_notification = TextField(max_length=1)
preparer_code = TextField(max_length=1)
blank6 = BlankField(max_length=12)
def validate_submitter_ein(self, f):
f.value = f.value.replace('-','')
excluded_values = ('07','08','09','17','18','19','28','29','49','69', '70', '78', '79', '89')
if f.value[0:2] in excluded_values:
raise ValidationError("%s not one of %s" % (f.value, excluded_values), field=f)
try:
int(f.value)
except ValueError:
raise ValidationError("%s must be numeric values only" % f.value, field=f)
def validate_resub_identifier(self, f):
if self.resub_indicator.value == True:
if not f.value:
raise ValidationError("resub_identifier must be set because resub_indicator is True", field=f)
def validate_preferred_notification(self, f):
if self.preferred_notification.value == '1':
if not self.contact_email.value:
raise ValidationError("contact_email must be set if preferred notification method is email (1)", field=f)
def validate_preparer_code(self, f):
valid_options = ('A','L','S','P','O')
if self.preparer_code.value.upper() not in valid_options:
raise ValidationError("preparer_code %s not one of %s" % (self.preparer_code.value, valid_options), field=f)
class EmployerRecord(model.Model):
record_identifier = 'RE'
required = True
tax_year = NumericField(max_length=4)
agent_indicator = NumericField(max_length=1, required=False)
employer_ein = TextField(max_length=9)
agent_for_ein = TextField(max_length=9, required=False)
terminating_business_indicator = BooleanField()
establishment_number = TextField(max_length=4, required=False)
other_ein = TextField(max_length=9, required=False)
employer_name = TextField(max_length=57)
location_address = TextField(max_length=22)
delivery_address = TextField(max_length=22)
city = TextField(max_length=22)
state = StateField()
zipcode = TextField(max_length=5)
zipcode_ext = TextField(max_length=4, required=False)
kind_of_employer = TextField(max_length=1)
blank1 = BlankField(max_length=4)
foreign_state_province = TextField(max_length=23)
foreign_postal_code = TextField(max_length=15)
country_code = TextField(max_length=2, required=False)
employment_code = TextField(max_length=1)
tax_jurisdiction_code = TextField(max_length=1, required=False)
third_party_sick_pay = BooleanField()
blank2 = BlankField(max_length=291)
def validate_agent_indicator(self, f):
v = f.value
if v and v not in (1,2,3):
raise ValidationError("%s not in one of (1,2,3)" % v, field=f)
def validate_employer_ein(self, f):
excluded_values = ('00','07','08','09','17','18','19','28','29','49','69','70','78','79','89')
if f.value[0:2] in excluded_values:
raise ValidationError("%s not one of %s" % (f.value, excluded_values), field=f)
def validate_agent_for_ein(self, f):
if self.agent_indicator.value == 1 and not f.value:
raise ValidationError("agent_for_ein must be provided with agent_indicator=1", field=f)
def validate_kind_of_employer(self, f):
choices = [k for k,v in enums.employer_types]
if f.value.upper() not in choices:
raise ValidationError("%s not in one of %s" % (f.value, choices), field=f)
def validate_employment_code(self, f):
choices = [k for k,v in enums.employment_codes]
if f.value.upper() not in choices:
raise ValidationError("%s not in one of %s" % (f.value, choices), field=f)
def validate_tax_jurisdiction_code(self, f):
choices = [k for k,v in enums.tax_jurisdiction_codes]
if f.value and f.value.upper() not in choices:
raise ValidationError("%s not in one of %s" % (f.value, choices), field=f)
class EmployeeWageRecord(model.Model):
record_identifier = 'RW'
required = True
ssn = NumericField(max_length=9, required=False)
employee_first_name = TextField(max_length=15)
employee_middle_name = TextField(max_length=15)
employee_last_name = TextField(max_length=20)
employee_suffix = TextField(max_length=4, required=False)
location_address = TextField(max_length=22)
delivery_address = TextField(max_length=22)
city = TextField(max_length=22)
state = StateField()
zipcode = TextField(max_length=5, required=False)
zipcode_ext = TextField(max_length=4, required=False)
blank1 = BlankField(max_length=5)
foreign_state = TextField(max_length=23, required=False)
foreign_postal_code = TextField(max_length=15, required=False)
country = TextField(max_length=2)
wages_tips = MoneyField(max_length=11)
federal_income_tax_withheld = MoneyField(max_length=11)
social_security_wages = MoneyField(max_length=11)
social_security_tax_withheld = MoneyField(max_length=11)
medicare_wages_and_tips = MoneyField(max_length=11)
medicare_tax_withheld = MoneyField(max_length=11)
social_security_tips = MoneyField(max_length=11)
advance_eic = MoneyField(max_length=11)
dependent_care_benefits = MoneyField(max_length=11)
deferred_compensation_401k = MoneyField(max_length=11)
deferred_compensation_403b = MoneyField(max_length=11)
deferred_compensation_408k = MoneyField(max_length=11)
deferred_compensation_457b = MoneyField(max_length=11)
deferred_compensation_501c = MoneyField(max_length=11)
military_pay = MoneyField(max_length=11)
non_qualified_457 = MoneyField(max_length=11)
employer_contrib_to_hsa = MoneyField(max_length=11)
non_qualified_not_457 = MoneyField(max_length=11)
nontaxable_combat_pay = MoneyField(max_length=11)
blank2 = BlankField(max_length=11)
cost_of_premiums_for_insurance = MoneyField(max_length=11)
income_nonstatutory_stock_opts = MoneyField(max_length=11)
deferred_compensation_409a = MoneyField(max_length=11)
designated_roth_contrib_401k = MoneyField(max_length=11)
designated_roth_contrib_403b = MoneyField(max_length=11)
blank3 = BlankField(max_length=23)
statutory_employee_indicator = BooleanField()
blank4 = BlankField(max_length=1)
retirement_plan_indicator = BooleanField()
third_party_sick_pay = BooleanField()
blank5 = BlankField(max_length=23)
def validate_ssn(self, f):
if str(f.value).startswith('666','9'):
raise ValidationError("ssn cannot start with 666 or 9", field=f)
class OptionalEmployeeWageRecord(model.Model):
record_identifier = 'RO'
required = False
blank1 = BlankField(max_length=9)
allocated_tips = MoneyField(max_length=11)
uncollected_tax_on_tips = MoneyField(max_length=11)
medical_savings_account = MoneyField(max_length=11)
simple_retirement_account = MoneyField(max_length=11)
qualified_adoption_expenses = MoneyField(max_length=11)
uncollected_ss_life_ins = MoneyField(max_length=11)
uncollected_medicare_life_ins = MoneyField(max_length=11)
income_under_409a = MoneyField(max_length=11)
hire_exempt_wages_and_tips = MoneyField(max_length=11)
blank2 = BlankField(max_length=164)
wages_subject_to_puerto_rico_tax = MoneyField(max_length=11, required=False)
commissions_subject_to_puerto_rico_tax = MoneyField(max_length=11, required=False)
allowances_subject_to_puerto_rico_tax = MoneyField(max_length=11, required=False)
tips_subject_to_puerto_rico_tax = MoneyField(max_length=11, required=False)
total_wages_subject_to_puerto_rico_tax = MoneyField(max_length=11, required=False)
puerto_rico_tax_withheld = MoneyField(max_length=11, required=False)
retirement_fund_contrib = MoneyField(max_length=11, required=False)
blank3 = BlankField(max_length=11)
total_wages_tips_virgin_islands = MoneyField(max_length=11, required=False)
virgin_islands_income_tax_withheld = MoneyField(max_length=11, required=False)
blank4 = BlankField(max_length=128)
class StateWageRecord(model.Model):
record_identifier = 'RS'
required = False
state_code = StateField(use_numeric=True)
taxing_entity_code = TextField(max_length=5, required=False)
ssn = NumericField(max_length=9, required=False)
employee_first_name = TextField(max_length=15)
employee_middle_name = TextField(max_length=15)
employee_last_name = TextField(max_length=20)
employee_suffix = TextField(max_length=4, required=False)
location_address = TextField(max_length=22)
delivery_address = TextField(max_length=22)
city = TextField(max_length=22)
state = StateField()
zipcode = TextField(max_length=5, required=False)
zipcode_ext = TextField(max_length=4, required=False)
blank1 = BlankField(max_length=5)
foreign_state_province = TextField(max_length=23, required=False)
foreign_postal_code = TextField(max_length=15, required=False)
country_code = TextField(max_length=2, required=False)
optional_code = TextField(max_length=2, required=False)
reporting_period = MonthYearField()
quarterly_unemp_ins_wages = MoneyField(max_length=11)
quarterly_unemp_ins_taxable_wages = MoneyField(max_length=11)
number_of_weeks_worked = NumericField(max_length=2)
date_first_employed = DateField(required=False)
date_of_separation = DateField(required=False)
blank2 = BlankField(max_length=5)
state_employer_account_num = NumericField(max_length=20)
blank3 = BlankField(max_length=6)
state_code_2 = StateField(use_numeric=True)
state_taxable_wages = MoneyField(max_length=11)
state_income_tax_wh = MoneyField(max_length=11)
other_state_data = TextField(max_length=10, required=False)
tax_type_code = TextField(max_length=1) # VALIDATE C, D, E, or F
local_taxable_wages = MoneyField(max_length=11)
local_income_tax_wh = MoneyField(max_length=11)
state_control_number = NumericField(max_length=7, required=False)
supplemental_data1 = TextField(max_length=75, required=False)
supplemental_data2 = TextField(max_length=75, required=False)
blank4 = BlankField(max_length=25)
def validate_tax_type_code(self, field):
choices [x for x,y in enums.tax_type_codes]
if field.value.upper() not in choices
raise ValidationError("%s not one of %s" % (field.value,choices), field=f)
class TotalRecord(model.Model):
record_identifier = 'RT'
required = True
number_of_rw_records = NumericField(max_length=7)
wages_tips = NumericField(max_length=15)
federal_income_tax_withheld = NumericField(max_length=15)
social_security_wages = NumericField(max_length=15)
social_security_tax_withheld = NumericField(max_length=15)
medicare_wages_and_tips = NumericField(max_length=15)
medicare_tax_withheld = NumericField(max_length=15)
social_security_tips = NumericField(max_length=15)
advance_eic = NumericField(max_length=15)
dependent_care_benefits = NumericField(max_length=15)
deferred_compensation_401k = NumericField(max_length=15)
deferred_compensation_403b = NumericField(max_length=15)
deferred_compensation_408k = NumericField(max_length=15)
deferred_compensation_457b = NumericField(max_length=15)
deferred_compensation_501c = NumericField(max_length=15)
military_pay = NumericField(max_length=15)
non_qualified_457 = NumericField(max_length=15)
employer_contrib_to_hsa = NumericField(max_length=15)
non_qualified_not_457 = NumericField(max_length=15)
nontaxable_combat_pay = NumericField(max_length=15)
blank1 = BlankField(max_length=15)
employer_cost_term_life_ins = NumericField(max_length=15)
income_tax_wh_sick_pay = NumericField(max_length=15)
income_exercise_nonstat_stock_opts = NumericField(max_length=15)
deferred_409a_compensation_plan = NumericField(max_length=15)
designated_roth_contribs_401k = NumericField(max_length=15)
disignated_roth_contribs_403b = NumericField(max_length=15)
blank2 = BlankField(max_length=113)
class OptionalTotalRecord(model.Model):
record_identifier = 'RU'
required = False
number_of_ro_records = NumericField(max_length=7)
allocated_tips = NumericField(max_length=15)
uncollected_tax_on_tips = NumericField(max_length=15)
medical_savings_account = NumericField(max_length=15)
simple_retirement_account = NumericField(max_length=15)
qualified_adoption_expenses = NumericField(max_length=15)
uncollected_ss_life_ins = NumericField(max_length=15)
uncollected_medicare_life_ins = NumericField(max_length=15)
income_under_409a = NumericField(max_length=15)
hire_exempt_wages_and_tips = NumericField(max_length=15)
blank1 = BlankField(max_length=210)
wages_subject_to_puerto_rico_tax = NumericField(max_length=15, required=False)
commissions_subject_to_puerto_rico_tax = NumericField(max_length=15, required=False)
allowances_subject_to_puerto_rico_tax = NumericField(max_length=15, required=False)
tips_subject_to_puerto_rico_tax = NumericField(max_length=15, required=False)
total_wages_subject_to_puerto_rico_tax = NumericField(max_length=15, required=False)
puerto_rico_tax_withheld = NumericField(max_length=15, required=False)
retirement_fund_contrib = NumericField(max_length=15, required=False)
total_wages_tips_virgin_islands = NumericField(max_length=15, required=False)
virgin_islands_income_tax_withheld = NumericField(max_length=15, required=False)
blank2 = BlankField(max_length=23)
class StateTotalRecord(model.Model):
record_identifier = 'RV'
required = False
supplemental_data = TextField(max_length=510)
class FinalRecord(model.Model):
record_identifier = 'RF'
required = True
blank1 = BlankField(max_length=5)
number_of_rw_records = NumericField(max_length=9)
blank2 = BlankField(max_length=496)