diff --git a/conservancy_beancount/plugin/meta_payroll_type.py b/conservancy_beancount/plugin/meta_payroll_type.py index d5ab11bde65bcd84d74348d30a6f934f2108f88b..5a6ec228e3d006156cc96bf132908c7544f3f461 100644 --- a/conservancy_beancount/plugin/meta_payroll_type.py +++ b/conservancy_beancount/plugin/meta_payroll_type.py @@ -14,56 +14,86 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from . import core import datetime + +from . import core from .. import ranges from .. import data from .. import errors as errormod + from ..beancount_types import ( Transaction, ) -class MetaPayrollType(core._PostingHook): - """Verify payroll-type metadata, starting on 2020-03-01 (FY 2020), is provided for the accounts: - Expenses:Payroll:Salary, Expenses:Payroll:Taxes, Expenses:Payroll:Benefits:HealthInsurance, and - Expenses:Payroll:Benefits:Other. - Valid values for payroll-type are listed in the - _SALARY_TYPES, _TAX_TYPES, _HEALTH_INSURANCE_TYPES, and _OTHER_BENEFIT_TYPES - (respectively) in the class. - """ - METADATA_KEY = 'payroll-type' - HOOK_GROUPS = frozenset(['metadata', METADATA_KEY]) - _TAX_TYPES = [ "CA:PP", "CA:EI", - "US:SocialSecurity", "US:Medicare", - "US:IL:Unemployment", - "US:MA:Unemployment", "US:MA:WorkTrain", "US:MA:Health", - "US:OR:Unemployment" ] - _SALARY_TYPES = [ "US:General", "US:PTO", "US:403b:Employee", "US:403b:Match", - "CA:General", "CA:PTO", - "CA:Taxes:Income", "CA:Taxes:EI", "CA:Taxes:PP", - "US:Taxes:Income", "US:Taxes:SocialSecurity", "US:Taxes:Medicare", - "US:IL:Taxes:Income", - "US:OR:Taxes:Income", "US:OR:Taxes:STT", - "US:MA:Taxes:Income", "US:MA:Disability:PML", "US:MA:Disability:PFL", - "US:NY:Taxes:Income", "US:NY:Disability:PFL", "US:NY:Disability", "US:NY:Taxes:NYC" ] - _HEALTH_INSURANCE_TYPES = [ "US:HRA:Fees", "US:HRA:Usage", "US:Premium:Main", "US:Premium:DentalVision" ] - _OTHER_BENEFIT_TYPES = [ "US:403b:Fees" ] - TXN_DATE_RANGE = ranges.DateRange(datetime.date(2020, 3, 1), core.DEFAULT_STOP_DATE) +METADATA_KEY = 'payroll-type' + +class _PayrollTypeHook(core._NormalizePostingMetadataHook): + ACCOUNT: str + VALUES_ENUM = core.MetadataEnum(METADATA_KEY, []) + TXN_DATE_RANGE = ranges.DateRange( + datetime.date(2020, 3, 1), + core.DEFAULT_STOP_DATE, + ) def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool: - if post.account.is_under('Expenses:Payroll:Salary') or post.account.is_under('Expenses:Payroll:Taxes') or \ - post.account.is_under('Expenses:Payroll:Benefits:Other') or post.account.is_under('Expenses:Payroll:Benefits:HealthInsurance'): - return True - else: - return False + return post.account.is_under(self.ACCOUNT) is not None + + +class HealthInsuranceHook(_PayrollTypeHook): + ACCOUNT = 'Expenses:Payroll:Benefits:HealthInsurance' + VALUES_ENUM = core.MetadataEnum(METADATA_KEY, [ + 'US:HRA:Fees', + 'US:HRA:Usage', + 'US:Premium:DentalVision', + 'US:Premium:Main', + ]) + + +class OtherBenefitsHook(_PayrollTypeHook): + ACCOUNT = 'Expenses:Payroll:Benefits:Other' + VALUES_ENUM = core.MetadataEnum(METADATA_KEY, [ + 'US:403b:Fees', + ]) + + +class SalaryHook(_PayrollTypeHook): + ACCOUNT = 'Expenses:Payroll:Salary' + VALUES_ENUM = core.MetadataEnum(METADATA_KEY, [ + 'CA:General', + 'CA:PTO', + 'CA:Taxes:EI', + 'CA:Taxes:Income', + 'CA:Taxes:PP', + 'US:403b:Employee', + 'US:403b:Match', + 'US:General', + 'US:IL:Taxes:Income', + 'US:MA:Disability:PFL', + 'US:MA:Disability:PML', + 'US:MA:Taxes:Income', + 'US:NY:Disability', + 'US:NY:Disability:PFL', + 'US:NY:Taxes:Income', + 'US:NY:Taxes:NYC', + 'US:OR:Taxes:Income', + 'US:OR:Taxes:STT', + 'US:PTO', + 'US:Taxes:Income', + 'US:Taxes:Medicare', + 'US:Taxes:SocialSecurity', + ]) + - def post_run(self, txn: Transaction, post: data.Posting) -> errormod.Iter: - value = post.meta.get(self.METADATA_KEY) - if value is None or \ - (post.account.is_under('Expenses:Payroll:Salary') and (not value in self._SALARY_TYPES)) or \ - (post.account.is_under('Expenses:Payroll:Taxes') and (not value in self._TAX_TYPES)) or \ - (post.account.is_under('Expenses:Payroll:Benefits:HealthInsurance') and \ - (not value in self._HEALTH_INSURANCE_TYPES)) or \ - (post.account.is_under('Expenses:Payroll:Benefits:Other') and \ - (not value in self._OTHER_BENEFIT_TYPES)): - yield errormod.InvalidMetadataError(txn, self.METADATA_KEY, value, post) +class TaxHook(_PayrollTypeHook): + ACCOUNT = 'Expenses:Payroll:Taxes' + VALUES_ENUM = core.MetadataEnum(METADATA_KEY, [ + 'CA:EI', + 'CA:PP', + 'US:IL:Unemployment', + 'US:MA:Health', + 'US:MA:Unemployment', + 'US:MA:WorkTrain', + 'US:Medicare', + 'US:OR:Unemployment', + 'US:SocialSecurity', + ])