Finished up most of the record order validation and also checking

for all required records in a set. Added a controller class but
decided to put stuff in __init__ instead, at least for now.
Added a DateField which converts datetime.date into the proper
string format for EFW2 files (hopefully), this should still be
tested next week.
This commit is contained in:
Binh 2011-05-07 15:19:48 -05:00
parent f30237a90d
commit 5781cbf335
4 changed files with 113 additions and 14 deletions

View file

@ -31,6 +31,18 @@ def test_dump():
return out return out
def test_record_order():
import record
records = [
record.SubmitterRecord(),
record.EmployerRecord(),
record.EmployeeWageRecord(),
record.TotalRecord(),
record.FinalRecord(),
]
verify_record_order(records)
def test_load(fp): def test_load(fp):
return load(fp) return load(fp)
@ -50,7 +62,6 @@ def load(fp):
record.read(fp) record.read(fp)
yield record yield record
def loads(s): def loads(s):
import StringIO import StringIO
fp = StringIO.StringIO(s) fp = StringIO.StringIO(s)
@ -61,7 +72,6 @@ def dump(records, fp):
for r in records: for r in records:
fp.write(r.output()) fp.write(r.output())
def dumps(records): def dumps(records):
import StringIO import StringIO
fp = StringIO.StringIO() fp = StringIO.StringIO()
@ -69,3 +79,65 @@ def dumps(records):
fp.seek(0) fp.seek(0)
return fp.read() return fp.read()
# THIS WAS IN CONTROLLER, BUT UNLESS WE
# REALLY NEED A CONTROLLER CLASS, IT'S SIMPLER
# TO JUST KEEP IT IN HERE.
def verify_required_records(records):
types = [rec.__class__.__name__ for rec in records]
req_types = []
for r in record.RECORD_TYPES:
klass = record.__dict__[r]
if klass.required:
req_types.append(klass.__name__)
while req_types:
req = req_types[0]
if req not in types:
raise ValidationError("Record set missing required record: %s" % req)
else:
req_types.remove(req)
def verify_record_order(records):
import record
from fields import ValidationError
# 1st record must be SubmitterRecord
if not isinstance(records[0], record.SubmitterRecord):
raise ValidationError("First record must be SubmitterRecord")
# 2nd record must be EmployeeRecord
if not isinstance(records[1], record.EmployerRecord):
raise ValidationError("The first record after SubmitterRecord must be an EmployeeRecord")
# FinalRecord - Must be the last record on the file
if not isinstance(records[-1], record.FinalRecord):
raise ValidationError("Last record must be a FinalRecord")
# an EmployerRecord *must* come after each EmployeeWageREcord
for i in range(len(records)):
if isinstance(records[i], record.EmployerRecord):
if not isinstance(records[i+1], record.EmployeeWageRecord):
raise ValidationError("All EmployerRecords must be followed by an EmployeeWageRecord")
num_ro_records = len(filter(lambda x:isinstance(x, record.OptionalEmployeeWageRecord), records))
num_ru_records = len(filter(lambda x:isinstance(x, record.OptionalTotalRecord), records))
num_employer_records = len(filter(lambda x:isinstance(x, record.EmployerRecord), records))
num_total_records = len(filter(lambda x: isinstance(x, record.TotalRecord), records))
# a TotalRecord is required for each instance of an EmployeeRecord
if num_total_records != num_employer_records:
raise ValidationError("Number of TotalRecords (%d) does not match number of EmployeeRecords (%d)" % (
num_total_records, num_employer_records))
# an OptionalTotalRecord is required for each OptionalEmployeeWageRecord
if num_ro_records != num_ru_records:
raise ValidationError("Number of OptionalEmployeeWageRecords (%d) does not match number OptionalTotalRecords (%d)" % (
num_ro_records, num_ru_records))
# FinalRecord - Must appear only once on each file.
if len(filter(lambda x:isinstance(x, record.FinalRecord), records)) != 1:
raise ValidationError("Incorrect number of FinalRecords")

View file

@ -1,4 +1,4 @@
import decimal import decimal, datetime
class ValidationError(Exception): class ValidationError(Exception):
pass pass
@ -123,5 +123,21 @@ class MoneyField(Field):
self.value = decimal.Decimal(s) * decimal.Decimal('0.01') self.value = decimal.Decimal(s) * decimal.Decimal('0.01')
class DateField(TextField): class DateField(TextField):
#FIXME I NEED TO BE WRITTEN! def __init__(self, name=None, required=True, value=None):
pass super(TextField, self).__init__(name=name, required=required, max_length=8)
if isinstance(value, datetime.date):
self._value = value
elif value:
self._value = datetime.date(*[int(x) for x in value[4:8], value[0:2], value[2:4]])
else:
self._value = None
def get_data(self):
if self._value:
return self._value.strftime('%m%d%Y')
return '0' * self.max_length
def parse(self, s):
self.value = datetime.date(*[int(x) for x in s[4:8], s[0:2], s[2:4]])

View file

@ -2,6 +2,7 @@ from fields import Field
class Model(object): class Model(object):
record_identifier = ' ' record_identifier = ' '
required = False
def __init__(self): def __init__(self):
for (key, value) in self.__class__.__dict__.items(): for (key, value) in self.__class__.__dict__.items():

View file

@ -1,13 +1,14 @@
import model import model
from fields import * from fields import *
__all__ = ['SubmitterRecord', 'EmployerRecord', __all__ = RECORD_TYPES = ['SubmitterRecord', 'EmployerRecord',
'EmployeeWageRecord', 'OptionalEmployeeWageRecord', 'EmployeeWageRecord', 'OptionalEmployeeWageRecord',
'TotalRecord', 'OptionalTotalRecord', 'TotalRecord', 'OptionalTotalRecord',
'StateTotalRecord', 'FinalRecord',] 'StateTotalRecord', 'FinalRecord',]
class SubmitterRecord(model.Model): class SubmitterRecord(model.Model):
record_identifier = 'RA' record_identifier = 'RA'
required = True
submitter_ein = NumericField(max_length=9) submitter_ein = NumericField(max_length=9)
user_id = TextField(max_length=8) user_id = TextField(max_length=8)
@ -51,6 +52,7 @@ class SubmitterRecord(model.Model):
class EmployerRecord(model.Model): class EmployerRecord(model.Model):
record_identifier = 'RE' record_identifier = 'RE'
required = True
tax_year = NumericField(max_length=4) tax_year = NumericField(max_length=4)
agent_indicator = NumericField(max_length=1) agent_indicator = NumericField(max_length=1)
@ -78,6 +80,7 @@ class EmployerRecord(model.Model):
class EmployeeWageRecord(model.Model): class EmployeeWageRecord(model.Model):
record_identifier = 'RW' record_identifier = 'RW'
required = True
ssn = NumericField(max_length=9, required=False) ssn = NumericField(max_length=9, required=False)
employee_first_name = TextField(max_length=15) employee_first_name = TextField(max_length=15)
@ -129,6 +132,7 @@ class EmployeeWageRecord(model.Model):
class OptionalEmployeeWageRecord(model.Model): class OptionalEmployeeWageRecord(model.Model):
record_identifier = 'RO' record_identifier = 'RO'
required = False
blank1 = BlankField(max_length=9) blank1 = BlankField(max_length=9)
allocated_tips = MoneyField(max_length=11) allocated_tips = MoneyField(max_length=11)
@ -156,6 +160,7 @@ class OptionalEmployeeWageRecord(model.Model):
class StateWageRecord(model.Model): class StateWageRecord(model.Model):
record_identifier = 'RS' record_identifier = 'RS'
required = False
state_code = NumericField(max_length=2) state_code = NumericField(max_length=2)
taxing_entity_code = TextField(max_length=5) taxing_entity_code = TextField(max_length=5)
@ -179,8 +184,8 @@ class StateWageRecord(model.Model):
quarterly_unemp_ins_wages = MoneyField(max_length=11) quarterly_unemp_ins_wages = MoneyField(max_length=11)
quarterly_unemp_ins_taxable_wages = MoneyField(max_length=11) quarterly_unemp_ins_taxable_wages = MoneyField(max_length=11)
number_of_weeks_worked = NumericField(max_length=2) number_of_weeks_worked = NumericField(max_length=2)
date_first_employed = TextField(max_length=8)#DateField() date_first_employed = DateField()
date_of_separation = TextField(max_length=8)#DateField() date_of_separation = DateField()
blank2 = BlankField(max_length=5) blank2 = BlankField(max_length=5)
state_employer_account_num = NumericField(max_length=20) state_employer_account_num = NumericField(max_length=20)
blank3 = BlankField(max_length=6) blank3 = BlankField(max_length=6)
@ -198,6 +203,7 @@ class StateWageRecord(model.Model):
class TotalRecord(model.Model): class TotalRecord(model.Model):
record_identifier = 'RT' record_identifier = 'RT'
required = True
number_of_rw_records = NumericField(max_length=7) number_of_rw_records = NumericField(max_length=7)
wages_tips = NumericField(max_length=15) wages_tips = NumericField(max_length=15)
@ -231,6 +237,7 @@ class TotalRecord(model.Model):
class OptionalTotalRecord(model.Model): class OptionalTotalRecord(model.Model):
record_identifier = 'RU' record_identifier = 'RU'
required = False
number_of_ro_records = NumericField(max_length=7) number_of_ro_records = NumericField(max_length=7)
allocated_tips = NumericField(max_length=15) allocated_tips = NumericField(max_length=15)
@ -257,10 +264,13 @@ class OptionalTotalRecord(model.Model):
class StateTotalRecord(model.Model): class StateTotalRecord(model.Model):
record_identifier = 'RV' record_identifier = 'RV'
required = False
supplemental_data = TextField(max_length=510) supplemental_data = TextField(max_length=510)
class FinalRecord(model.Model): class FinalRecord(model.Model):
record_identifier = 'RF' record_identifier = 'RF'
required = True
blank1 = BlankField(max_length=5) blank1 = BlankField(max_length=5)
number_of_rw_records = NumericField(max_length=9) number_of_rw_records = NumericField(max_length=9)