From ff7af2f06e8840f4682f97eddf5cb3acd89216bd Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Mon, 2 Jun 2025 20:39:41 +1000 Subject: [PATCH] austax: Spread income tax expense out across 12 months --- libdrcr/plugins/austax/reporting.luau | 130 ++++++++++++++++++++------ libdrcr/plugins/libdrcr.luau | 9 ++ 2 files changed, 109 insertions(+), 30 deletions(-) diff --git a/libdrcr/plugins/austax/reporting.luau b/libdrcr/plugins/austax/reporting.luau index 0d7f75b..f666fd3 100644 --- a/libdrcr/plugins/austax/reporting.luau +++ b/libdrcr/plugins/austax/reporting.luau @@ -15,6 +15,16 @@ -- You should have received a copy of the GNU Affero General Public License -- along with this program. If not, see . +----------------- +-- Flags + +-- true = Spread income tax expense over monthly transactions +-- false = Charge income tax expense in one transaction at end of financial year +local charge_tax_monthly = true + +----------------- +-- Reporting code + local libdrcr = require('../libdrcr') local account_kinds = require('../austax/account_kinds') local calc = require('../austax/calc') @@ -377,9 +387,68 @@ function reporting.CalculateIncomeTax.execute(args, context, kinds_for_account, }}) -- Generate income tax transactions - local transactions: {libdrcr.Transaction} = { - { - -- # Estimated tax payable + local transactions: {libdrcr.Transaction} = {} + + -- Estimated tax payable + if charge_tax_monthly then + -- Charge income tax expense in parts, one per month + local monthly_tax = math.floor((tax_total - offset_total) / 12) + local last_month_tax = (tax_total - offset_total) - 11 * monthly_tax -- To account for rounding errors + + -- Some ad hoc calendar code + local eofy_year, eofy_month, _ = libdrcr.parse_date(context.eofy_date) + local last_day_of_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } -- Leap years handled below + + for month = 1, 12 do + local this_year, this_month_tax + if month == eofy_month then + this_year = eofy_year + this_month_tax = last_month_tax + elseif month < eofy_month then + this_year = eofy_year + this_month_tax = monthly_tax + else + this_year = eofy_year - 1 + this_month_tax = monthly_tax + end + + local this_day = last_day_of_month[month] + + -- Check for leap year + if month == 2 and (this_year % 4 == 0) and (this_year % 100 ~= 0 or this_year % 400 == 0) then + this_day = 29 + end + + -- Charge monthly tax + table.insert(transactions, { + id = nil, + dt = libdrcr.date_to_dt(libdrcr.format_date(this_year, month, this_day)), + description = 'Estimated income tax', + postings = { + { + id = nil, + transaction_id = nil, + description = nil, + account = INCOME_TAX, + quantity = this_month_tax, + commodity = context.reporting_commodity, + quantity_ascost = this_month_tax, + }, + { + id = nil, + transaction_id = nil, + description = nil, + account = INCOME_TAX_CONTROL, + quantity = -this_month_tax, + commodity = context.reporting_commodity, + quantity_ascost = -this_month_tax, + }, + }, + }) + end + else + -- Charge income tax expense in one transaction at EOFY + table.insert(transactions, { id = nil, dt = libdrcr.date_to_dt(context.eofy_date), description = 'Estimated income tax', @@ -403,34 +472,35 @@ function reporting.CalculateIncomeTax.execute(args, context, kinds_for_account, quantity_ascost = -(tax_total - offset_total), }, }, - }, - { - -- Mandatory study loan repayment - id = nil, - dt = libdrcr.date_to_dt(context.eofy_date), - description = 'Mandatory study loan repayment payable', - postings = { - { - id = nil, - transaction_id = nil, - description = nil, - account = HELP, - quantity = study_loan_repayment, - commodity = context.reporting_commodity, - quantity_ascost = study_loan_repayment, - }, - { - id = nil, - transaction_id = nil, - description = nil, - account = INCOME_TAX_CONTROL, - quantity = -study_loan_repayment, - commodity = context.reporting_commodity, - quantity_ascost = -study_loan_repayment, - }, + }) + end + + -- Mandatory study loan repayment + table.insert(transactions, { + id = nil, + dt = libdrcr.date_to_dt(context.eofy_date), + description = 'Mandatory study loan repayment payable', + postings = { + { + id = nil, + transaction_id = nil, + description = nil, + account = HELP, + quantity = study_loan_repayment, + commodity = context.reporting_commodity, + quantity_ascost = study_loan_repayment, }, - } - } + { + id = nil, + transaction_id = nil, + description = nil, + account = INCOME_TAX_CONTROL, + quantity = -study_loan_repayment, + commodity = context.reporting_commodity, + quantity_ascost = -study_loan_repayment, + }, + }, + }) -- Transfer PAYGW balances to Income Tax Control for account, kinds in pairs(kinds_for_account) do diff --git a/libdrcr/plugins/libdrcr.luau b/libdrcr/plugins/libdrcr.luau index 472dcc3..19dfced 100644 --- a/libdrcr/plugins/libdrcr.luau +++ b/libdrcr/plugins/libdrcr.luau @@ -149,6 +149,7 @@ export type MultipleDateStartDateEndArgs = { dates: {DateStartDateEndArgs} } local libdrcr = {} +-- Returns true if array haystack contains needle function libdrcr.arr_contains(haystack: {any}, needle: any): boolean for _, element in ipairs(haystack) do if element == needle then @@ -158,10 +159,17 @@ function libdrcr.arr_contains(haystack: {any}, needle: any): boolean return false end +-- Converts a date string (YYYY-MM-DD) into datetime string (YYYY-MM-DD HH:MM:SS.xxxxxx) for database function libdrcr.date_to_dt(date: string): string return date .. ' 00:00:00.000000' end +-- Formats the date as date string (YYYY-MM-DD) +function libdrcr.format_date(year: number, month: number, day: number): string + return string.format('%04d-%02d-%02d', year, month, day) +end + +-- Parses the date string (YYYY-MM-DD) into components function libdrcr.parse_date(date: string): (number, number, number) local year_str, month_str, day_str = string.match(date, '(%d%d%d%d)-(%d%d)-(%d%d)') @@ -176,6 +184,7 @@ function libdrcr.parse_date(date: string): (number, number, number) return year, month, day end +-- Convert the Lua value to string recursively function libdrcr.repr(value: any): string local result = '' if type(value) == 'table' then