Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
370 lines (332 sloc) 15.4 KB
from docassemble.base.core import DAObject, DAList, DADict, DAOrderedDict
from docassemble.base.util import Value, PeriodicValue, FinancialList, PeriodicFinancialList
from decimal import Decimal
import datetime
import docassemble.base.functions
from collections import OrderedDict
def flatten(listname,index=1):
"""Return just the nth item in an 2D list. Intended to use for multiple choice option lists in Docassemble.
e.g., flatten(asset_type_list()) will return ['Savings','Certificate of Deposit'...] """
return [item[index] for item in listname]
def income_period_list():
return [
[12,"Monthly"],
[1,"Yearly"],
[52,"Weekly"],
[24,"Twice per month"],
[26,"Once every two weeks"],
[4,"Once every 3 months"]
]
docassemble.base.functions.update_language_function('*', 'period_list', income_period_list)
def recent_years(years=15, order='descending',future=1):
"""Returns a list of the most recent years, continuing into the future. Defaults to most recent 15 years+1. Useful to populate
a combobox of years where the most recent ones are most likely. E.g. automobile years or birthdate.
Keyword paramaters: years, order (descending or ascending), future (defaults to 1)"""
now = datetime.datetime.now()
if order=='ascending':
return list(range(now.year-years,now.year+future,1))
else:
return list(range(now.year+future,now.year-years,-1))
def asset_type_list() :
"""Returns a list of assset types for a multiple choice dropdown"""
type_list = DAOrderedDict()
type_list.auto_gather = False
type_list.gathered = True
type_list.elements.update([
('savings', 'Savings Account'),
('cd', 'Certificate of Deposit'),
('ira', 'Individual Retirement Account'),
('mutual fund', 'Money or Mutual Fund'),
('stocks', 'Stocks or Bonds'),
('trust', 'Trust Fund'),
('checking', 'Checking Account'),
('vehicle', 'Vehicle'),
('real estate', 'Real Estate'),
('other', 'Other Asset')
])
return type_list
def income_type_list() :
"""Returns a dict of income types for a multiple choice dropdown"""
type_list = DAOrderedDict()
type_list['wages'] = 'A job or self-employment'
type_list.elements.update(non_wage_income_list())
type_list.auto_gather = False
type_list.gathered = True
return type_list
def non_wage_income_list():
"""Returns a dict of income types, excluding wages"""
type_list = DAOrderedDict()
type_list.auto_gather = False
type_list.gathered = True
type_list.elements.update([
('SSR', 'Social Security Retirement Benefits'),
('SSDI', 'Social Security Disability Benefits'),
('SSI', 'Supplemental Security Income (SSI)'),
('pension', 'Pension'),
('TAFDC', 'TAFDC'),
('public assistance', 'Other public assistance'),
('SNAP', 'Food Stamps (SNAP)'),
('rent', 'Income from real estate (rent, etc)'),
('room and board', 'Room and/or Board Payments'),
('child support', 'Child Support'),
('alimony', 'Alimony'),
('other support', 'Other Support'),
('other', 'Other')
])
return type_list
def expense_type_list() :
"""Returns a dict of expense types for a multiple choice dropdown"""
type_list = DAOrderedDict()
type_list.auto_gather = False
type_list.gathered = True
type_list.elements.update([
('rent', 'Rent'),
('mortgage', 'Mortgage'),
('food', 'Food'),
('utilities', 'Utilities'),
('fuel', 'Other Heating/Cooking Fuel'),
('clothing', 'Clothing'),
('credit cards', 'Credit Card Payments'),
('property tax', 'Property Tax (State and Local)'),
('other taxes', 'Other taxes and fees related to your home'),
('insurance', 'Insurance'),
('medical', 'Medical-Dental (after amount paid by insurance)'),
('auto', 'Car operation and maintenance'),
('transportation', 'Other transportation'),
('charity', 'Church or charitable donations'),
('loan payments', 'Loan, credit, or lay-away payments'),
('support', 'Support to someone not in household'),
('other', 'Other')
])
return type_list
class Income(PeriodicValue):
"""Represents a job which may have an hourly rate or a salary.
Hourly rate jobs must include hours and period.
Period is some demoninator of a year for compatibility with
PeriodicFinancialList class. E.g, to express hours/week, use 52 """
def amount(self, period_to_use=1):
"""Returns the amount earned over the specified period """
# Can't remember why I added the below so let's see what commenting
# it out breaks...
#if not hasattr(self, 'value') or self.value == '':
# value = 0
#else:
# value = self.value
#if not hasattr(self, 'period') or self.period == '':
# period = 1
#else:
# period = self.period
if hasattr(self, 'is_hourly') and self.is_hourly:
return Decimal(self.hourly_rate * self.hours_per_period * self.period) / Decimal(period_to_use)
return (Decimal(self.value) * Decimal(self.period)) / Decimal(period_to_use)
class Job(Income):
"""Represents a job that may be hourly or pay-period based. If non-hourly, may specify gross and net income amounts"""
def net_amount(self, period_to_use=1):
"""Returns the net amount (e.g., minus deductions). Only applies if value is non-hourly."""
return (Decimal(self.net) * Decimal(self.period)) / Decimal(period_to_use)
def gross_amount(self, period_to_use=1):
"""Gross amount is identical to value"""
return self.amount(period_to_use = period_to_use)
def name_address_phone(self):
"""Returns concatenation of name, address and phone number of employer"""
return self.employer + ': ' + self.employer_address + ', ' + self.employer_phone
def normalized_hours(self, period_to_use):
"""Returns the number of hours worked in a given period"""
return (float(self.hours_per_period) * int(self.period)) / int(period_to_use)
class SimpleValue(DAObject):
"""Like a Value object, but no fiddling around with .exists attribute because it's designed to store in a list, not a dictionary"""
def amount(self):
"""If desired, to use as a ledger, values can be signed. setting transaction_type = 'expense' makes the value negative. Use min=0 in that case."""
if hasattr(self, 'transaction_type'):
return (self.value * -1) if (self.transaction_type == 'expense') else self.value
else:
return self.value
def __str__(self):
if hasattr(self, 'transaction_type'):
return str( (self.value * -1) ) if (self.transaction_type == 'expense') else str ( self.value )
else:
return str( self.value )
class Vehicle(SimpleValue):
"""Vehicles have a method year_make_model() """
def year_make_model(self):
return self.year + ' / ' + self.make + ' / ' + self.model
class ValueList(DAList):
"""Represents a filterable DAList of SimpleValues"""
def init(self, *pargs, **kwargs):
super(ValueList, self).init(*pargs, **kwargs)
self.object_type = SimpleValue
def types(self):
"""Returns a set of the unique types of values stored in the list. Will fail if any items in the list leave the type field unspecified"""
types = set()
for item in self.elements:
if hasattr(item,'type'):
types.add(item.type)
return types
def total(self, type=None):
"""Returns the total value in the list, gathering the list items if necessary.
You can specify type, which may be a list, to coalesce multiple entries of the same type."""
self._trigger_gather()
result = 0
if type is None:
for item in self.elements:
#if self.elements[item].exists:
result += Decimal(item.amount())
elif isinstance(type, list):
for item in self.elements:
if item.type in type:
result += Decimal(item.amount())
else:
for item in self.elements:
if item.type == type:
result += Decimal(item.amount())
return result
class Ledger(ValueList):
"""Represents an account ledger. Adds calculate method which adds a running total to the ledger."""
def init(self, *pargs, **kwargs):
super(Ledger, self).init(*pargs, **kwargs)
def calculate(self):
""" Sort the ledger by date, then add a running total to each ledger entry"""
self.elements.sort(key=lambda y: y.date)
running_total = 0
for entry in self.elements:
running_total += entry.amount()
entry.running_total = running_total
class VehicleList(ValueList):
"""List of vehicles, extends ValueList. Vehicles have a method year_make_model() """
def init(self, *pargs, **kwargs):
super(VehicleList, self).init(*pargs, **kwargs)
self.object_type = Vehicle
class IncomeList(DAList):
"""Represents a filterable DAList of income items, each of which has an associated period or hourly wages."""
def init(self, *pargs, **kwargs):
self.elements = list()
if not hasattr(self, 'object_type'):
self.object_type = Income
return super(IncomeList, self).init(*pargs, **kwargs)
def types(self):
"""Returns a set of the unique types of values stored in the list."""
types = set()
for item in self.elements:
if hasattr(item,'type'):
types.add(item.type)
return types
def owners(self, type=None):
"""Returns a set of the unique owners for the specified type of value stored in the list. If type is None, returns all
unique owners in the IncomeList"""
owners=set()
if type is None:
for item in self.elements:
if hasattr(item, 'owner'):
owners.add(item.owner)
elif isinstance(type, list):
for item in self.elements:
if hasattr(item,'owner') and hasattr(item,'type') and item.type in type:
owners.add(item.owner)
else:
for item in self.elements:
if hasattr(item,'owner') and item.type == type:
owners.add(item.owner)
return owners
def matches(self, type):
"""Returns an IncomeList consisting only of elements matching the specified Income type, assisting in filling PDFs with predefined spaces. Type may be a list"""
if isinstance(type, list):
return IncomeList(elements = [item for item in self.elements if item.type in type])
else:
return IncomeList(elements = [item for item in self.elements if item.type == type])
def total(self, period_to_use=1, type=None,owner=None):
"""Returns the total periodic value in the list, gathering the list items if necessary.
You can specify type, which may be a list, to coalesce multiple entries of the same type.
Similarly, you can specify owner."""
self._trigger_gather()
result = 0
if period_to_use == 0:
return(result)
if type is None:
for item in self.elements:
#if self.elements[item].exists:
result += Decimal(item.amount(period_to_use=period_to_use))
elif isinstance(type, list):
for item in self.elements:
if item.type in type:
if owner is None:
result += Decimal(item.amount(period_to_use=period_to_use))
else:
if item.owner == owner:
result += Decimal(item.amount(period_to_use=period_to_use))
else:
for item in self.elements:
if item.type == type:
if owner is None:
result += Decimal(item.amount(period_to_use=period_to_use))
else:
if item.owner == owner:
result += Decimal(item.amount(period_to_use=period_to_use))
return result
def market_value_total(self, type=None):
"""Returns the total market value of values in the list."""
result = 0
for item in self.elements:
if type is None:
result += Decimal(item.market_value)
elif isinstance(type, list):
if item.type in type:
result += Decimal(item.market_value)
else:
if item.type == type:
result += Decimal(item.market_value)
return result
def balance_total(self, type=None):
self._trigger_gather()
result = 0
for item in self.elements:
if type is None:
result += Decimal(item.balance)
elif isinstance(type, list):
if item.type in type:
result += Decimal(item.balance)
else:
if item.type == type:
result += Decimal(item.balance)
return result
class JobList(IncomeList):
"""Represents a list of jobs. Adds the net_total and gross_total methods to the IncomeList class"""
def init(self, *pargs, **kwargs):
# self.elements = list()
self.object_type = Job
return super(JobList, self).init(*pargs, **kwargs)
def gross_total(self, period_to_use=1, type=None):
self._trigger_gather()
result = 0
if period_to_use == 0:
return(result)
if type is None:
for item in self.elements:
#if self.elements[item].exists:
result += Decimal(item.gross_amount(period_to_use=period_to_use))
elif isinstance(type, list):
for item in self.elements:
if item.type in type:
result += Decimal(item.gross_amount(period_to_use=period_to_use))
else:
for item in self.elements:
if item.type == type:
result += Decimal(item.gross_amount(period_to_use=period_to_use))
return result
def net_total(self, period_to_use=1, type=None):
self._trigger_gather()
result = 0
if period_to_use == 0:
return(result)
if type is None:
for item in self.elements:
#if self.elements[item].exists:
result += Decimal(item.net_amount(period_to_use=period_to_use))
elif isinstance(type, list):
for item in self.elements:
if item.type in type:
result += Decimal(item.net_amount(period_to_use=period_to_use))
else:
for item in self.elements:
if item.type == type:
result += Decimal(item.net_amount(period_to_use=period_to_use))
return result
You can’t perform that action at this time.