From 9d41f79c52282892b345597584d98c92a3bb9075 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Tue, 3 Nov 2020 15:24:06 -0800 Subject: [PATCH] meta_payroll_type: Basic validation support for payroll-type --- conservancy_beancount/plugin/__init__.py | 1 + .../plugin/meta_payroll_type.py | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 conservancy_beancount/plugin/meta_payroll_type.py diff --git a/conservancy_beancount/plugin/__init__.py b/conservancy_beancount/plugin/__init__.py index 9183c80..9a19be5 100644 --- a/conservancy_beancount/plugin/__init__.py +++ b/conservancy_beancount/plugin/__init__.py @@ -67,6 +67,7 @@ class HookRegistry: '.meta_repo_links': None, '.meta_rt_links': ['MetaRTLinks'], '.meta_tax_implication': None, + '.meta_payroll_type': None, '.txn_date': ['TransactionDate'], } diff --git a/conservancy_beancount/plugin/meta_payroll_type.py b/conservancy_beancount/plugin/meta_payroll_type.py new file mode 100644 index 0000000..d5ab11b --- /dev/null +++ b/conservancy_beancount/plugin/meta_payroll_type.py @@ -0,0 +1,69 @@ +"""meta_payroll_type - Validate payroll-type metadata""" +# Copyright © 2020 Bradley M. Kuhn, Brett Smith +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# 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 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) + + 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 + + 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)