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:
parent
f30237a90d
commit
5781cbf335
4 changed files with 113 additions and 14 deletions
76
__init__.py
76
__init__.py
|
@ -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")
|
||||||
|
|
||||||
|
|
||||||
|
|
22
fields.py
22
fields.py
|
@ -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]])
|
||||||
|
|
||||||
|
|
||||||
|
|
1
model.py
1
model.py
|
@ -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():
|
||||||
|
|
16
record.py
16
record.py
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue