From 7ed4d20d87868d5e3918b5d2cccf6558e2849ca6 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Thu, 22 Nov 2012 19:40:36 -0500 Subject: Began summary reports script, starting with a basic balance sheet. --- .../non-profit-audit-reports/summary-reports.plx | 168 +++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100755 contrib/non-profit-audit-reports/summary-reports.plx (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx new file mode 100755 index 00000000..1b9bc734 --- /dev/null +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -0,0 +1,168 @@ +#!/usr/bin/perl +# fund-report.plx -*- Perl -*- +# +# Script to generate end-of-year summary reports. +# +# Copyright (C) 2011, 2012, Bradley M. Kuhn +# +# This program gives you software freedom; you can copy, modify, convey, +# and/or redistribute it under the terms of the GNU 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 +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program in a file called 'GPLv3'. If not, write to the: +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor +# Boston, MA 02110-1301, USA. + +use strict; +use warnings; + +use Math::BigFloat; + +my $VERBOSE = 0; +my $DEBUG = 0; + +my $LEDGER_BIN = "/usr/local/bin/ledger"; + +my $ACCT_WIDTH = 70; + +sub Commify ($) { + my $text = reverse $_[0]; + $text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g; + return scalar reverse $text; +} + +sub ParseNumber($) { + $_[0] =~ s/,//g; + return Math::BigFloat->new($_[0]); +} +Math::BigFloat->precision(-2); +my $ZERO = Math::BigFloat->new("0.00"); + +if (@ARGV < 2) { + print STDERR "usage: $0 \n"; + exit 1; +} +my($startDate, $endDate, @mainLedgerOptions) = @ARGV; + +# First, get fund list from ending balance +my(@ledgerOptions) = (@mainLedgerOptions, + '-V', '-X', '$', '-F', "%-.70A %22.108t\n", '-S', 'T', '-s', + 'd', 'T', '-e', $endDate, 'bal', '/^Assets/'); + +my %reportFields = + ('Cash' => { args => [ '-e', $endDate, 'bal', '/^Assets/' ] }, + 'Accounts Receivable' => {args => [ '-e', $endDate, 'bal', '/^Accrued:Accounts Receivable/' ]}, + 'Accounts Payable' => {args => [ '-e', $endDate, 'bal', '/^Accrued.*Accounts Payable/' ]}, + 'Accrued Expenses' => {args => [ '-e', $endDate, 'bal', '/^Accrued.*Expenses/' ]}, + 'Unearned Income, Conference Registration' => {args => [ '-e', $endDate, 'bal', + '/^Unearned Income.*Conf.*Reg/' ]}, + 'Unearned Income, Other' => {args => [ '-e', $endDate, 'bal', '/^Unearned Income/', 'and', 'not', + '/^Unearned Income.*Conf.*Reg/' ]}, + 'Unrestricted Net Assets' => {args => [ '-e', $endDate, 'bal', '/^(Income|Expenses):Conservancy/' ]}, + 'Temporarily Restricted Net Assets' => {args => [ '-e', $endDate, 'bal', '/^(Income|Expenses)/', + 'and', 'not', '/^(Unearned Income|(Income|Expenses):Conservancy)/' ]}, + 'Total Net Assets' => {args => [ '-e', $endDate, 'bal', '/^(Income|Expenses)/' ]}, + +); +foreach my $item (keys %reportFields) { + my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, + '-V', '-S', 'T', '-s', '-d', 'T', @{$reportFields{$item}{args}}); + open(FILE, "-|", @fullCommand) + or die "unable to run command ledger command: @fullCommand: $!"; + + my $foundBalance; + my $seenTotalLine = 0; + + print STDERR ($VERBOSE ? "Running: @fullCommand\n" : "."); + print STDERR " Output of @fullCommand\n" if $DEBUG; + + while (my $line = ) { + print STDERR $line if ($DEBUG); + + $seenTotalLine = 1 if $line =~ /^\s*\-+\s*/; # Skip lines until the total line + $foundBalance = $1 + if (not $seenTotalLine and $line =~ /^\s*[^0-9\-]+\s*([\-\d,\.]+)\s+/); + + if ($line =~ /^\s*\$\s*([\-\d,\.]+)\s*$/) { + $foundBalance = $1; + last; + } + } + close FILE; + die "problem running ledger command: @fullCommand: $!" unless ($? == 0); + if (not defined $foundBalance) { + $foundBalance = $ZERO; + } else { + $foundBalance =~ s/,//g; + $foundBalance = Math::BigFloat->new($foundBalance); + } + $foundBalance = $ZERO if not defined $foundBalance; + $reportFields{$item}{total} = abs($foundBalance); + print STDERR "$item: $reportFields{$item}{total}\n" if $VERBOSE; +} + +die "Cash+accounts receivable total does not equal net assets and liabilities total" + if ( ($reportFields{'Cash'}{total} + $reportFields{'Accounts Receivable'}{total}) != + ($reportFields{'Accounts Payable'}{total} + + $reportFields{'Accrued Expenses'}{total} + + $reportFields{'Unearned Income, Conference Registration'}{total} + + $reportFields{'Unearned Income, Other'}{total} + + $reportFields{'Total Net Assets'}{total})); + +die "Total net assets doesn't equal sum of restricted and unrestricted ones!" + if ($reportFields{'Total Net Assets'}{total} != + $reportFields{'Unrestricted Net Assets'}{total} + + $reportFields{'Temporarily Restricted Net Assets'}{total}); + +open(ASSETS, ">", "assets-and-liabilities.txt") + or die "unable to open assets-and-liabilities.txt for writing: $!"; + +print ASSETS "ASSETS\n\n"; + +my $formatStr = " %-42s \$%18s\n"; +my $formatStrTotal = "%-45s \$%12s\n"; +my $tot = $ZERO; +foreach my $item ('Cash', 'Accounts Receivable') { + next if $reportFields{$item}{total} == $ZERO; + print ASSETS sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); + $tot += $reportFields{$item}{total}; +} +print ASSETS "\n", sprintf($formatStrTotal, "TOTAL ASSETS", Commify($tot)), "\n\nLIABILITIES\n\n"; + +my $totLiabilities = $ZERO; +foreach my $item ('Accounts Payable', 'Accrued Expenses', + 'Unearned Income, Conference Registration', 'Unearned Income, Other') { + next if $reportFields{$item}{total} == $ZERO; + print ASSETS sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); + $totLiabilities += $reportFields{$item}{total}; +} +print ASSETS "\n", sprintf($formatStr, "TOTAL LIABILTIES", Commify($totLiabilities)), + "\n\nNET ASSETS\n\n"; + +my $totNetAssets = $ZERO; +foreach my $item ('Unrestricted Net Assets', 'Temporarily Restricted Net Assets') { + next if $reportFields{$item}{total} == $ZERO; + print ASSETS sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); + $totNetAssets += $reportFields{$item}{total}; +} +print ASSETS "\n", sprintf($formatStr, "TOTAL NET ASSETS", Commify($totNetAssets)), "\n\n", + sprintf($formatStrTotal, "TOTAL LIABILITIES AND NET ASSETS", + Commify($totNetAssets + $totLiabilities)); + +close ASSETS; +print STDERR "\n"; +die "unable to write to Assets-and-liabilities.txt: $!" unless ($? == 0); + +############################################################################### +# +# Local variables: +# compile-command: "perl -c summary-reports.plx" +# End: + -- cgit v1.2.3 From f0ee16a373412e3654120e3847430a1a94ea8d23 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 23 Nov 2012 08:35:38 -0500 Subject: Add Loans. --- contrib/non-profit-audit-reports/summary-reports.plx | 1 + 1 file changed, 1 insertion(+) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 1b9bc734..a175f5ba 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -59,6 +59,7 @@ my(@ledgerOptions) = (@mainLedgerOptions, my %reportFields = ('Cash' => { args => [ '-e', $endDate, 'bal', '/^Assets/' ] }, 'Accounts Receivable' => {args => [ '-e', $endDate, 'bal', '/^Accrued:Accounts Receivable/' ]}, + 'Loans Receivable' => {args => [ '-e', $endDate, 'bal', '/^Accrued:Loans Receivable/' ]}, 'Accounts Payable' => {args => [ '-e', $endDate, 'bal', '/^Accrued.*Accounts Payable/' ]}, 'Accrued Expenses' => {args => [ '-e', $endDate, 'bal', '/^Accrued.*Expenses/' ]}, 'Unearned Income, Conference Registration' => {args => [ '-e', $endDate, 'bal', -- cgit v1.2.3 From 7b0e4c48067296d15c6f350948cbf29d7aec5787 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 23 Nov 2012 08:35:45 -0500 Subject: Add -X $. --- contrib/non-profit-audit-reports/summary-reports.plx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index a175f5ba..c0731322 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -74,7 +74,7 @@ my %reportFields = ); foreach my $item (keys %reportFields) { my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, - '-V', '-S', 'T', '-s', '-d', 'T', @{$reportFields{$item}{args}}); + '-V', '-X', '$', '-S', 'T', '-s', '-d', 'T', @{$reportFields{$item}{args}}); open(FILE, "-|", @fullCommand) or die "unable to run command ledger command: @fullCommand: $!"; -- cgit v1.2.3 From cba448b28bbc26a03a5b590818ff8668ac162681 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 23 Nov 2012 08:36:11 -0500 Subject: Move sanity checks to the bottom, after report is generated. Also, fix formatting. --- .../non-profit-audit-reports/summary-reports.plx | 33 +++++++++++----------- 1 file changed, 17 insertions(+), 16 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index c0731322..0f0e09ee 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -109,28 +109,15 @@ foreach my $item (keys %reportFields) { print STDERR "$item: $reportFields{$item}{total}\n" if $VERBOSE; } -die "Cash+accounts receivable total does not equal net assets and liabilities total" - if ( ($reportFields{'Cash'}{total} + $reportFields{'Accounts Receivable'}{total}) != - ($reportFields{'Accounts Payable'}{total} + - $reportFields{'Accrued Expenses'}{total} + - $reportFields{'Unearned Income, Conference Registration'}{total} + - $reportFields{'Unearned Income, Other'}{total} + - $reportFields{'Total Net Assets'}{total})); - -die "Total net assets doesn't equal sum of restricted and unrestricted ones!" - if ($reportFields{'Total Net Assets'}{total} != - $reportFields{'Unrestricted Net Assets'}{total} + - $reportFields{'Temporarily Restricted Net Assets'}{total}); - open(ASSETS, ">", "assets-and-liabilities.txt") or die "unable to open assets-and-liabilities.txt for writing: $!"; print ASSETS "ASSETS\n\n"; -my $formatStr = " %-42s \$%18s\n"; -my $formatStrTotal = "%-45s \$%12s\n"; +my $formatStr = " %-42s \$%13s\n"; +my $formatStrTotal = "%-45s \$%13s\n"; my $tot = $ZERO; -foreach my $item ('Cash', 'Accounts Receivable') { +foreach my $item ('Cash', 'Accounts Receivable', 'Loans Receivable') { next if $reportFields{$item}{total} == $ZERO; print ASSETS sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); $tot += $reportFields{$item}{total}; @@ -161,6 +148,20 @@ close ASSETS; print STDERR "\n"; die "unable to write to Assets-and-liabilities.txt: $!" unless ($? == 0); +die "Cash+accounts receivable total does not equal net assets and liabilities total" + if ( ($reportFields{'Cash'}{total} + $reportFields{'Accounts Receivable'}{total} + + $reportFields{'Loans Receivable'}{total}) != + ($reportFields{'Accounts Payable'}{total} + + $reportFields{'Accrued Expenses'}{total} + + $reportFields{'Unearned Income, Conference Registration'}{total} + + $reportFields{'Unearned Income, Other'}{total} + + $reportFields{'Total Net Assets'}{total})); + +die "Total net assets doesn't equal sum of restricted and unrestricted ones!" + if ($reportFields{'Total Net Assets'}{total} != + $reportFields{'Unrestricted Net Assets'}{total} + + $reportFields{'Temporarily Restricted Net Assets'}{total}); + ############################################################################### # # Local variables: -- cgit v1.2.3 From 46b13e8e550fb05a3e863e913cf6cd359ef42272 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 23 Nov 2012 08:44:53 -0500 Subject: Include credit card balances in the Liabilities list. --- contrib/non-profit-audit-reports/summary-reports.plx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 0f0e09ee..04923de8 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -62,6 +62,7 @@ my %reportFields = 'Loans Receivable' => {args => [ '-e', $endDate, 'bal', '/^Accrued:Loans Receivable/' ]}, 'Accounts Payable' => {args => [ '-e', $endDate, 'bal', '/^Accrued.*Accounts Payable/' ]}, 'Accrued Expenses' => {args => [ '-e', $endDate, 'bal', '/^Accrued.*Expenses/' ]}, + 'Liabilities, Credit Cards' => {args => [ '-e', $endDate, 'bal', '/^Liabilities:Credit Card/' ]}, 'Unearned Income, Conference Registration' => {args => [ '-e', $endDate, 'bal', '/^Unearned Income.*Conf.*Reg/' ]}, 'Unearned Income, Other' => {args => [ '-e', $endDate, 'bal', '/^Unearned Income/', 'and', 'not', @@ -125,7 +126,7 @@ foreach my $item ('Cash', 'Accounts Receivable', 'Loans Receivable') { print ASSETS "\n", sprintf($formatStrTotal, "TOTAL ASSETS", Commify($tot)), "\n\nLIABILITIES\n\n"; my $totLiabilities = $ZERO; -foreach my $item ('Accounts Payable', 'Accrued Expenses', +foreach my $item ('Accounts Payable', 'Liabilities, Credit Cards', 'Accrued Expenses', 'Unearned Income, Conference Registration', 'Unearned Income, Other') { next if $reportFields{$item}{total} == $ZERO; print ASSETS sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); @@ -155,6 +156,7 @@ die "Cash+accounts receivable total does not equal net assets and liabilities to $reportFields{'Accrued Expenses'}{total} + $reportFields{'Unearned Income, Conference Registration'}{total} + $reportFields{'Unearned Income, Other'}{total} + + $reportFields{'Liabilities, Credit Cards'}{total} + $reportFields{'Total Net Assets'}{total})); die "Total net assets doesn't equal sum of restricted and unrestricted ones!" -- cgit v1.2.3 From ccd5d06c04576bbd3911d4ce0c9ccb8c4ec4cfc5 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 23 Nov 2012 08:45:52 -0500 Subject: Include any other liabilities that aren't credit cards. --- contrib/non-profit-audit-reports/summary-reports.plx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 04923de8..14923a42 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -63,6 +63,8 @@ my %reportFields = 'Accounts Payable' => {args => [ '-e', $endDate, 'bal', '/^Accrued.*Accounts Payable/' ]}, 'Accrued Expenses' => {args => [ '-e', $endDate, 'bal', '/^Accrued.*Expenses/' ]}, 'Liabilities, Credit Cards' => {args => [ '-e', $endDate, 'bal', '/^Liabilities:Credit Card/' ]}, + 'Liabilities, Other' => {args => [ '-e', $endDate, 'bal', '/^Liabilities/', + 'and', 'not', '/^Liabilities:Credit Card/']}, 'Unearned Income, Conference Registration' => {args => [ '-e', $endDate, 'bal', '/^Unearned Income.*Conf.*Reg/' ]}, 'Unearned Income, Other' => {args => [ '-e', $endDate, 'bal', '/^Unearned Income/', 'and', 'not', @@ -126,7 +128,8 @@ foreach my $item ('Cash', 'Accounts Receivable', 'Loans Receivable') { print ASSETS "\n", sprintf($formatStrTotal, "TOTAL ASSETS", Commify($tot)), "\n\nLIABILITIES\n\n"; my $totLiabilities = $ZERO; -foreach my $item ('Accounts Payable', 'Liabilities, Credit Cards', 'Accrued Expenses', +foreach my $item ('Accounts Payable', 'Accrued Expenses', + 'Liabilities, Credit Cards', 'Liabilities, Other', 'Unearned Income, Conference Registration', 'Unearned Income, Other') { next if $reportFields{$item}{total} == $ZERO; print ASSETS sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); @@ -157,6 +160,7 @@ die "Cash+accounts receivable total does not equal net assets and liabilities to $reportFields{'Unearned Income, Conference Registration'}{total} + $reportFields{'Unearned Income, Other'}{total} + $reportFields{'Liabilities, Credit Cards'}{total} + + $reportFields{'Liabilities, Other'}{total} + $reportFields{'Total Net Assets'}{total})); die "Total net assets doesn't equal sum of restricted and unrestricted ones!" -- cgit v1.2.3 From 23dd0899f2634cad221b1859f127a0a6c96d62a2 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 23 Nov 2012 08:48:32 -0500 Subject: Allow for one penny margin of error on totals. --- contrib/non-profit-audit-reports/summary-reports.plx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 14923a42..875f89be 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -44,6 +44,7 @@ sub ParseNumber($) { } Math::BigFloat->precision(-2); my $ZERO = Math::BigFloat->new("0.00"); +my $ONE_PENNY = Math::BigFloat->new("0.01"); if (@ARGV < 2) { print STDERR "usage: $0 \n"; @@ -153,20 +154,20 @@ print STDERR "\n"; die "unable to write to Assets-and-liabilities.txt: $!" unless ($? == 0); die "Cash+accounts receivable total does not equal net assets and liabilities total" - if ( ($reportFields{'Cash'}{total} + $reportFields{'Accounts Receivable'}{total} - + $reportFields{'Loans Receivable'}{total}) != - ($reportFields{'Accounts Payable'}{total} + + if (abs( ($reportFields{'Cash'}{total} + $reportFields{'Accounts Receivable'}{total} + + $reportFields{'Loans Receivable'}{total})) - + abs($reportFields{'Accounts Payable'}{total} + $reportFields{'Accrued Expenses'}{total} + $reportFields{'Unearned Income, Conference Registration'}{total} + $reportFields{'Unearned Income, Other'}{total} + $reportFields{'Liabilities, Credit Cards'}{total} + $reportFields{'Liabilities, Other'}{total} + - $reportFields{'Total Net Assets'}{total})); + $reportFields{'Total Net Assets'}{total}) > $ONE_PENNY); die "Total net assets doesn't equal sum of restricted and unrestricted ones!" - if ($reportFields{'Total Net Assets'}{total} != - $reportFields{'Unrestricted Net Assets'}{total} + - $reportFields{'Temporarily Restricted Net Assets'}{total}); + if (abs($reportFields{'Total Net Assets'}{total}) - + abs($reportFields{'Unrestricted Net Assets'}{total} + + $reportFields{'Temporarily Restricted Net Assets'}{total}) > $ONE_PENNY); ############################################################################### # -- cgit v1.2.3 From e0df353ca37e4601921341a692300754186c8e6e Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 23 Nov 2012 08:53:02 -0500 Subject: Call a Balance Sheet, a Balance Sheet. :) --- contrib/non-profit-audit-reports/summary-reports.plx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 875f89be..27a8d210 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -113,43 +113,43 @@ foreach my $item (keys %reportFields) { print STDERR "$item: $reportFields{$item}{total}\n" if $VERBOSE; } -open(ASSETS, ">", "assets-and-liabilities.txt") - or die "unable to open assets-and-liabilities.txt for writing: $!"; +open(BALANCE_SHEET, ">", "balance-sheet.txt") + or die "unable to open balance-sheet.txt for writing: $!"; -print ASSETS "ASSETS\n\n"; +print BALANCE_SHEET "ASSETS\n\n"; my $formatStr = " %-42s \$%13s\n"; my $formatStrTotal = "%-45s \$%13s\n"; my $tot = $ZERO; foreach my $item ('Cash', 'Accounts Receivable', 'Loans Receivable') { next if $reportFields{$item}{total} == $ZERO; - print ASSETS sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); + print BALANCE_SHEET sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); $tot += $reportFields{$item}{total}; } -print ASSETS "\n", sprintf($formatStrTotal, "TOTAL ASSETS", Commify($tot)), "\n\nLIABILITIES\n\n"; +print BALANCE_SHEET "\n", sprintf($formatStrTotal, "TOTAL ASSETS", Commify($tot)), "\n\nLIABILITIES\n\n"; my $totLiabilities = $ZERO; foreach my $item ('Accounts Payable', 'Accrued Expenses', 'Liabilities, Credit Cards', 'Liabilities, Other', 'Unearned Income, Conference Registration', 'Unearned Income, Other') { next if $reportFields{$item}{total} == $ZERO; - print ASSETS sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); + print BALANCE_SHEET sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); $totLiabilities += $reportFields{$item}{total}; } -print ASSETS "\n", sprintf($formatStr, "TOTAL LIABILTIES", Commify($totLiabilities)), +print BALANCE_SHEET "\n", sprintf($formatStr, "TOTAL LIABILTIES", Commify($totLiabilities)), "\n\nNET ASSETS\n\n"; my $totNetAssets = $ZERO; foreach my $item ('Unrestricted Net Assets', 'Temporarily Restricted Net Assets') { next if $reportFields{$item}{total} == $ZERO; - print ASSETS sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); + print BALANCE_SHEET sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); $totNetAssets += $reportFields{$item}{total}; } -print ASSETS "\n", sprintf($formatStr, "TOTAL NET ASSETS", Commify($totNetAssets)), "\n\n", +print BALANCE_SHEET "\n", sprintf($formatStr, "TOTAL NET ASSETS", Commify($totNetAssets)), "\n\n", sprintf($formatStrTotal, "TOTAL LIABILITIES AND NET ASSETS", Commify($totNetAssets + $totLiabilities)); -close ASSETS; +close BALANCE_SHEET; print STDERR "\n"; die "unable to write to Assets-and-liabilities.txt: $!" unless ($? == 0); -- cgit v1.2.3 From 239df56cfba6ad715f4fc63f9fde6dd968e973cd Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 23 Nov 2012 09:01:19 -0500 Subject: Output should include a heading and an ending date. --- contrib/non-profit-audit-reports/summary-reports.plx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 27a8d210..c41fee50 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -24,6 +24,7 @@ use strict; use warnings; use Math::BigFloat; +use Date::Manip; my $VERBOSE = 0; my $DEBUG = 0; @@ -113,10 +114,16 @@ foreach my $item (keys %reportFields) { print STDERR "$item: $reportFields{$item}{total}\n" if $VERBOSE; } +my $err; open(BALANCE_SHEET, ">", "balance-sheet.txt") or die "unable to open balance-sheet.txt for writing: $!"; -print BALANCE_SHEET "ASSETS\n\n"; +print BALANCE_SHEET " BALANCE SHEET\n", + " Ending ", + UnixDate(DateCalc(ParseDate($endDate), ParseDateDelta("- 1 day"), \$err), + "%B %e, %Y\n"), + "\n\nASSETS\n\n"; +die "Date calculation error" if ($err); my $formatStr = " %-42s \$%13s\n"; my $formatStrTotal = "%-45s \$%13s\n"; -- cgit v1.2.3 From fe608b12e2a3b774bc7e56b40d95444bcf0d476e Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 23 Nov 2012 09:02:35 -0500 Subject: Remove cruft cut-and-pasted from another script. --- contrib/non-profit-audit-reports/summary-reports.plx | 5 ----- 1 file changed, 5 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index c41fee50..154662f1 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -53,11 +53,6 @@ if (@ARGV < 2) { } my($startDate, $endDate, @mainLedgerOptions) = @ARGV; -# First, get fund list from ending balance -my(@ledgerOptions) = (@mainLedgerOptions, - '-V', '-X', '$', '-F', "%-.70A %22.108t\n", '-S', 'T', '-s', - 'd', 'T', '-e', $endDate, 'bal', '/^Assets/'); - my %reportFields = ('Cash' => { args => [ '-e', $endDate, 'bal', '/^Assets/' ] }, 'Accounts Receivable' => {args => [ '-e', $endDate, 'bal', '/^Accrued:Accounts Receivable/' ]}, -- cgit v1.2.3 From 76292d08d92365a10964255a01a60ee1f6aa7448 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 23 Nov 2012 09:07:12 -0500 Subject: Calculate dates in a reusable way throughout script. --- contrib/non-profit-audit-reports/summary-reports.plx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 154662f1..b49e6bdd 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -53,6 +53,14 @@ if (@ARGV < 2) { } my($startDate, $endDate, @mainLedgerOptions) = @ARGV; +my $err; +my $formattedEndDate = UnixDate(DateCalc(ParseDate($endDate), ParseDateDelta("- 1 day"), \$err), + "%B %e, %Y"); +die "Date calculation error on $endDate" if ($err); + +my $formattedStartDate = UnixDate(ParseDate($endDate), "%B %e, %Y"), +die "Date calculation error on $startDate" if ($err); + my %reportFields = ('Cash' => { args => [ '-e', $endDate, 'bal', '/^Assets/' ] }, 'Accounts Receivable' => {args => [ '-e', $endDate, 'bal', '/^Accrued:Accounts Receivable/' ]}, @@ -109,16 +117,12 @@ foreach my $item (keys %reportFields) { print STDERR "$item: $reportFields{$item}{total}\n" if $VERBOSE; } -my $err; open(BALANCE_SHEET, ">", "balance-sheet.txt") or die "unable to open balance-sheet.txt for writing: $!"; print BALANCE_SHEET " BALANCE SHEET\n", - " Ending ", - UnixDate(DateCalc(ParseDate($endDate), ParseDateDelta("- 1 day"), \$err), - "%B %e, %Y\n"), + " Ending ", $formattedEndDate, "\n", "\n\nASSETS\n\n"; -die "Date calculation error" if ($err); my $formatStr = " %-42s \$%13s\n"; my $formatStrTotal = "%-45s \$%13s\n"; -- cgit v1.2.3 From 13c8a1fb569ddd83a65645e40c7f0c83b3a725eb Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 23 Nov 2012 09:53:05 -0500 Subject: Beginnings of income report. --- contrib/non-profit-audit-reports/summary-reports.plx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index b49e6bdd..9cb2aece 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -175,9 +175,26 @@ die "Total net assets doesn't equal sum of restricted and unrestricted ones!" abs($reportFields{'Unrestricted Net Assets'}{total} + $reportFields{'Temporarily Restricted Net Assets'}{total}) > $ONE_PENNY); + +my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-b', $startDate, '-e', $endDate, + '-F', '%-.80A %22.108t\n', '-s', + 'reg', '^Income'); + +open(FILE, "-|", @fullCommand) + or die "unable to run command ledger command: @fullCommand: $!"; + +open(INCOME, ">", "income.txt") + or die "unable to open balance-sheet.txt for writing: $!"; + +print INCOME " INCOME\n", + " Between $formattedStartDate and $formattedEndDate\n\n"; + +foreach my $line () { print INCOME $line; } +close INCOME; +die "unable to write to income.txt: $!" unless ($? == 0); ############################################################################### # # Local variables: # compile-command: "perl -c summary-reports.plx" # End: - -- cgit v1.2.3 From 7467917c7b93a39bbd90ccb059a9b55fdfbe52c4 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Sat, 24 Nov 2012 13:57:06 -0500 Subject: Generate income report. --- .../non-profit-audit-reports/summary-reports.plx | 81 ++++++++++++++++++---- 1 file changed, 67 insertions(+), 14 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 9cb2aece..a30868d5 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -57,8 +57,7 @@ my $err; my $formattedEndDate = UnixDate(DateCalc(ParseDate($endDate), ParseDateDelta("- 1 day"), \$err), "%B %e, %Y"); die "Date calculation error on $endDate" if ($err); - -my $formattedStartDate = UnixDate(ParseDate($endDate), "%B %e, %Y"), +my $formattedStartDate = UnixDate(ParseDate($startDate), "%B %e, %Y"); die "Date calculation error on $startDate" if ($err); my %reportFields = @@ -157,7 +156,7 @@ print BALANCE_SHEET "\n", sprintf($formatStr, "TOTAL NET ASSETS", Commify($totNe close BALANCE_SHEET; print STDERR "\n"; -die "unable to write to Assets-and-liabilities.txt: $!" unless ($? == 0); +die "unable to write to balance-sheet.txt: $!" unless ($? == 0); die "Cash+accounts receivable total does not equal net assets and liabilities total" if (abs( ($reportFields{'Cash'}{total} + $reportFields{'Accounts Receivable'}{total} @@ -176,23 +175,77 @@ die "Total net assets doesn't equal sum of restricted and unrestricted ones!" $reportFields{'Temporarily Restricted Net Assets'}{total}) > $ONE_PENNY); -my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', - '-b', $startDate, '-e', $endDate, - '-F', '%-.80A %22.108t\n', '-s', - 'reg', '^Income'); +my %incomeGroups = ('INTEREST INCOME' => { args => ['/^Income.*Interest/' ] }, + 'DONATIONS' => { args => [ '/^Income.*Donation/' ] }, + 'BOOK ROYALTIES & AFFILIATE PROGRAMS' => + { args => [ '/^Income.*(Royalt|Affilate)/' ] }, + 'CONFERENCES, REGISTRATION' => {args => [ '/^Income.*Conf.*Reg/' ] }, + 'CONFERENCES, RELATED BUSINESS INCOME' => { args => [ '/^Income.*(Booth|RBI)/'] }, + 'LICENSE ENFORCEMENT' => { args => [ '/^Income.*Enforce/' ]}, + 'TRADEMARKS' => {args => [ '/^Income.*Trademark/' ]}, + 'ADVERSITING' => {args => [ '/^Income.*Advertising/' ]}); + +my @otherArgs; +foreach my $type (keys %incomeGroups) { + @otherArgs = ("/^Income/") if @otherArgs == 0; + push(@otherArgs, 'and', 'not', @{$incomeGroups{$type}{args}}); +} +$incomeGroups{"OTHER"}{args} = \@otherArgs; +$incomeGroups{"TOTAL"}{args} = ['/^Income/']; + +open(INCOME, ">", "income.txt") or die "unable to open income.txt for writing: $!"; -open(FILE, "-|", @fullCommand) - or die "unable to run command ledger command: @fullCommand: $!"; +foreach my $type (keys %incomeGroups) { + my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-b', $startDate, '-e', $endDate, + '-F', '%-.80A %22.108t\n', '-s', + 'reg', @{$incomeGroups{$type}{args}}); -open(INCOME, ">", "income.txt") - or die "unable to open balance-sheet.txt for writing: $!"; + open(FILE, "-|", @fullCommand) + or die "unable to run command ledger command: @fullCommand: $!"; + + print STDERR ($VERBOSE ? "Running: @fullCommand\n" : "."); + $incomeGroups{$type}{total} = $ZERO; + $incomeGroups{$type}{output} = ""; + + foreach my $line () { + die "Unable to parse output line from second funds command: $line" + unless $line =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/; + my($account, $amount) = ($1, $2); + $amount = ParseNumber($amount); + $account =~ s/\s+$//; + next if $account =~ /\/ and (abs($amount) <= 0.02); + die "Weird account found, $account with amount of $amount in income command\n" + unless $account =~ s/^\s*Income://; + + $incomeGroups{$type}{total} += $amount; + $incomeGroups{$type}{output} .= " $line"; + } +} print INCOME " INCOME\n", " Between $formattedStartDate and $formattedEndDate\n\n"; -foreach my $line () { print INCOME $line; } -close INCOME; -die "unable to write to income.txt: $!" unless ($? == 0); + +my $overallTotal = $ZERO; + +$formatStrTotal = "%-90s \$%14s\n"; +foreach my $type ('DONATIONS', 'LICENSE ENFORCEMENT', + 'CONFERENCES, REGISTRATION', 'CONFERENCES, RELATED BUSINESS INCOME', + 'BOOK ROYALTIES & AFFILIATE PROGRAMS', 'ADVERSITING', + 'TRADEMARKS', 'INTEREST INCOME', 'OTHER') { + print INCOME "\n$type\n", + $incomeGroups{$type}{output}, "\n", + sprintf($formatStrTotal, "TOTAL $type:", Commify($incomeGroups{$type}{total})); + $overallTotal += $incomeGroups{$type}{total}; +} +print INCOME "\n\n\n", sprintf($formatStrTotal, "OVERALL TOTAL:", Commify($overallTotal)); + +close INCOME; die "unable to write to income.txt: $!" unless ($? == 0); + +die "calculated total of $overallTotal does equal $incomeGroups{TOTAL}{total}" + if (abs($overallTotal) - abs($incomeGroups{TOTAL}{total}) > $ONE_PENNY); + ############################################################################### # # Local variables: -- cgit v1.2.3 From 470ed356be4b92bb2967eab281d187f03691ad48 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Sun, 25 Nov 2012 08:41:14 -0500 Subject: Expenses report completed. --- .../non-profit-audit-reports/summary-reports.plx | 101 +++++++++++++++++++++ 1 file changed, 101 insertions(+) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index a30868d5..7b521ccb 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -234,6 +234,7 @@ foreach my $type ('DONATIONS', 'LICENSE ENFORCEMENT', 'CONFERENCES, REGISTRATION', 'CONFERENCES, RELATED BUSINESS INCOME', 'BOOK ROYALTIES & AFFILIATE PROGRAMS', 'ADVERSITING', 'TRADEMARKS', 'INTEREST INCOME', 'OTHER') { + next if ($incomeGroups{$type}{output} =~ /^\s*$/ and $incomeGroups{$type}{total} == $ZERO); print INCOME "\n$type\n", $incomeGroups{$type}{output}, "\n", sprintf($formatStrTotal, "TOTAL $type:", Commify($incomeGroups{$type}{total})); @@ -246,6 +247,106 @@ close INCOME; die "unable to write to income.txt: $!" unless ($? == 0); die "calculated total of $overallTotal does equal $incomeGroups{TOTAL}{total}" if (abs($overallTotal) - abs($incomeGroups{TOTAL}{total}) > $ONE_PENNY); +print STDERR "\n"; + +my %expenseGroups = ('BANKING FEES' => { regex => '^Expenses.*(Banking Fees|Currency Conversion)' }, + 'COMPUTING, HOSTING AND EQUIPMENT' => { regex => '^Expenses.*(Hosting|Computer Equipment)' }, + 'CONFERENCES' => { regex => '^Expenses.*(Conferences|Sprint)' }, + 'DEVELOPER MENTORING' => {regex => '^Expenses.*Mentor' }, + 'LICENSE ENFORCEMENT' => { regex => '^Expenses.*Enforce' }, + 'ACCOUNTING' => { regex => '^Expenses.*(Accounting|Annual Audit)' }, + 'PAYROLL' => { regex => '^Expenses.*Payroll' }, + 'OFFICE' => { regex => '^Expenses.*(Office|Phones)' }, + 'RENT' => { regex => '^Expenses.*Rent' }, + 'SOFTWARE DEVELOPMENT' => { regex => '^Expenses.*Development' }, + 'OTHER PROGRAM ACTIVITY' => {regex => '^Expenses.*Gould' }, + 'ADVOCACY AND PROMOTION' => {regex => '^Expenses.*(Slipstream|Advocacy Merchandise|Promotional)' }, + 'ADVERSITING' => {regex => '^Expenses.*Advertising' }); + +foreach my $type (keys %expenseGroups, 'TRAVEL') { + $expenseGroups{$type}{total} = $ZERO; + $expenseGroups{$type}{output} = ""; +} + +open(EXPENSE, ">", "expense.txt") or die "unable to open expense.txt for writing: $!"; + +my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-b', $startDate, '-e', $endDate, + '-F', '%-.80A %22.108t\n', '-s', + 'reg', '/^Expenses/'); + +open(FILE, "-|", @fullCommand) + or die "unable to run command ledger command: @fullCommand: $!"; + +print STDERR ($VERBOSE ? "Running: @fullCommand\n" : "."); + +my $firstTotal = $ZERO; +foreach my $line () { + die "Unable to parse output line from second funds command: $line" + unless $line =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/; + my($account, $amount) = ($1, $2); + $amount = ParseNumber($amount); + $account =~ s/\s+$//; + next if $account =~ /\/ and (abs($amount) <= 0.02); + die "Weird account found, $account, with amount of $amount in expenses command\n" + unless $account =~ /^\s*Expenses:/; + + if ($account =~ /Travel/) { + $expenseGroups{'TRAVEL'}{total} += $amount; + $expenseGroups{'TRAVEL'}{output} .= " $line"; + } else { + my $taken = 0; + foreach my $type (keys %expenseGroups) { + last if $taken; + next if $type eq 'TRAVEL' or $type eq 'OTHER'; + next unless $line =~ /$expenseGroups{$type}{regex}/; + $taken = 1; + $expenseGroups{$type}{total} += $amount; + $expenseGroups{$type}{output} .= " $line"; + } + if (not $taken) { + $expenseGroups{'OTHER'}{total} += $amount; + $expenseGroups{'OTHER'}{output} .= " $line"; + } + } + $firstTotal += $amount; +} +print EXPENSE " EXPENSES\n", + " Between $formattedStartDate and $formattedEndDate\n\n"; +$overallTotal = $ZERO; +$formatStrTotal = "%-90s \$%14s\n"; + +my %verifyAllGroups; +foreach my $key (keys %expenseGroups) { + $verifyAllGroups{$key} = 1; +} +foreach my $type ('PAYROLL', 'SOFTWARE DEVELOPMENT', 'LICENSE ENFORCEMENT', 'CONFERENCES', + 'DEVELOPER MENTORING', 'TRAVEL', 'BANKING FEES', 'ADVOCACY AND PROMOTION', + 'COMPUTING, HOSTING AND EQUIPMENT', 'ACCOUNTING', + 'OFFICE', 'RENT', 'ADVERSITING', 'OTHER PROGRAM ACTIVITY', 'OTHER') { + delete $verifyAllGroups{$type}; + + die "$type is not defined!" if not defined $expenseGroups{$type}; + next if ($expenseGroups{$type}{output} =~ /^\s*$/ and $expenseGroups{$type}{total} == $ZERO); + + print EXPENSE "\n$type\n", + $expenseGroups{$type}{output}, "\n", + sprintf($formatStrTotal, "TOTAL $type:", Commify($expenseGroups{$type}{total})); + $overallTotal += $expenseGroups{$type}{total}; +} + +print EXPENSE "\n\n\n", sprintf($formatStrTotal, "OVERALL TOTAL:", Commify($overallTotal)); + +close EXPENSE; die "unable to write to expense.txt: $!" unless ($? == 0); + +die "GROUPS NOT INCLUDED : ", join(keys(%verifyAllGroups), ", "), "\n" + unless (keys %verifyAllGroups == 0); + +die "calculated total of $overallTotal does equal $firstTotal" + if (abs($overallTotal) - abs($firstTotal) > $ONE_PENNY); + +print STDERR "\n"; + ############################################################################### # # Local variables: -- cgit v1.2.3 From 4318c11fd9b94a3508fac7cd7724407f2f7f6645 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Sun, 25 Nov 2012 12:19:44 -0500 Subject: Expense report favors Conferences first, then takes Travel as if it were an Other category only after categories have been handled. --- .../non-profit-audit-reports/summary-reports.plx | 29 +++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 7b521ccb..7d2267d6 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -291,20 +291,21 @@ foreach my $line () { die "Weird account found, $account, with amount of $amount in expenses command\n" unless $account =~ /^\s*Expenses:/; - if ($account =~ /Travel/) { - $expenseGroups{'TRAVEL'}{total} += $amount; - $expenseGroups{'TRAVEL'}{output} .= " $line"; - } else { - my $taken = 0; - foreach my $type (keys %expenseGroups) { - last if $taken; - next if $type eq 'TRAVEL' or $type eq 'OTHER'; - next unless $line =~ /$expenseGroups{$type}{regex}/; - $taken = 1; - $expenseGroups{$type}{total} += $amount; - $expenseGroups{$type}{output} .= " $line"; - } - if (not $taken) { + my $taken = 0; + # Note: Prioritize to put things under conference expenses if they were for a conference. + foreach my $type ('CONFERENCES', keys %expenseGroups) { + last if $taken; + next if $type eq 'TRAVEL' or $type eq 'OTHER'; + next unless $line =~ /$expenseGroups{$type}{regex}/; + $taken = 1; + $expenseGroups{$type}{total} += $amount; + $expenseGroups{$type}{output} .= " $line"; + } + if (not $taken) { + if ($account =~ /Travel/) { + $expenseGroups{'TRAVEL'}{total} += $amount; + $expenseGroups{'TRAVEL'}{output} .= " $line"; + } else { $expenseGroups{'OTHER'}{total} += $amount; $expenseGroups{'OTHER'}{output} .= " $line"; } -- cgit v1.2.3 From 269d0fdd5efe2dc9de148f845f97ad95ddc1b8e9 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Thu, 3 Jan 2013 10:39:58 -0500 Subject: Created Trial balance report for summary reports. --- .../non-profit-audit-reports/summary-reports.plx | 31 +++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 7d2267d6..ce6d56da 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -3,7 +3,7 @@ # # Script to generate end-of-year summary reports. # -# Copyright (C) 2011, 2012, Bradley M. Kuhn +# Copyright (C) 2011, 2012, 2013, Bradley M. Kuhn # # This program gives you software freedom; you can copy, modify, convey, # and/or redistribute it under the terms of the GNU General Public License @@ -348,6 +348,35 @@ die "calculated total of $overallTotal does equal $firstTotal" print STDERR "\n"; +open(TRIAL, ">", "trial-balance.txt") or die "unable to open accrued.txt for writing: $!"; + +@fullCommand = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-e', $endDate, + '-F', '%-.80A %22.108t\n', '-s', + 'reg'); + +print TRIAL " TRIAL BALANCE \n", + " Ending $formattedEndDate\n\n"; + +open(FILE, "-|", @fullCommand) + or die "unable to run command ledger command: @fullCommand: $!"; + +print STDERR ($VERBOSE ? "Running: @fullCommand\n" : "."); + +my $accruedTotal = $ZERO; + +foreach my $line () { + die "Unable to parse output line from second funds command: $line" + unless $line =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/; + my($account, $amount) = ($1, $2); + $amount = ParseNumber($amount); + $account =~ s/\s+$//; + next if $account =~ /\|^Equity:/ and (abs($amount) <= 0.02); + print TRIAL $line; + + $accruedTotal += $amount; +} + ############################################################################### # # Local variables: -- cgit v1.2.3 From bfdf20b31cbb60b5d92c82ecd3a5192059a0c7b1 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Thu, 3 Jan 2013 11:44:55 -0500 Subject: Updated sorting function based on advice of auditing accountants. Our auditing accounts tell us they want accounts sorted by: Assets Liabilities Net Assets Income Expenses in a general ledger report. Generally, I think we should just apply the same sorting. Since Ledger doesn't use account codes by default, this is my hack to solve this problem for now. Maybe there should be an account code tag for sorting purposes at least? --- .../general-ledger-report.plx | 46 ++++++++++++++++------ .../non-profit-audit-reports/summary-reports.plx | 30 ++++++++++++++ 2 files changed, 63 insertions(+), 13 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/general-ledger-report.plx b/contrib/non-profit-audit-reports/general-ledger-report.plx index d1c92975..66fc0031 100755 --- a/contrib/non-profit-audit-reports/general-ledger-report.plx +++ b/contrib/non-profit-audit-reports/general-ledger-report.plx @@ -4,8 +4,8 @@ # Script to generate a General Ledger report that accountants like # using Ledger. # -# Copyright (C) 2011, 2012 Bradley M. Kuhn -# Copyright (C) 2012 Tom Marble +# Copyright (C) 2011, 2012, 2013 Bradley M. Kuhn +# Copyright (C) 2012 Tom Marble # # This program gives you software freedom; you can copy, modify, convey, # and/or redistribute it under the terms of the GNU General Public License @@ -67,18 +67,38 @@ close(CHART_DATA); die "error reading ledger output for chart of accounts: $!" u open(CHART_OUTPUT, ">", "chart-of-accounts.txt") or die "unable to write chart-of-accounts.txt: $!"; print MANIFEST "chart-of-accounts.txt\n"; +sub preferredAccountSorting ($$) { + if ($_[0] =~ /^Assets/) { + return -1; + } elsif ($_[1] =~ /^Assets/) { + return 1; + } elsif ($_[0] =~ /^Liabilities/ and $_[1] !~ /^Assets/) { + return -1; + } elsif ($_[1] =~ /^Liabilities/ and $_[0] !~ /^Assets/) { + return 1; + } elsif ($_[0] =~ /^(Accrued)/ and $_[1] !~ /^(Assets|Liabilities)/) { + return -1; + } elsif ($_[1] =~ /^(Accrued)/ and $_[0] !~ /^(Assets|Liabilities)/) { + return 1; + } elsif ($_[0] =~ /^(Unearned Income)/ and $_[1] !~ /^(Assets|Liabilities|Accrued)/) { + return -1; + } elsif ($_[1] =~ /^(Unearned Income)/ and $_[0] !~ /^(Assets|Liabilities|Accrued)/) { + return 1; + } elsif ($_[0] =~ /^Income/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + return -1; + } elsif ($_[1] =~ /^Income/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + return 1; + } elsif ($_[0] =~ /^Expense/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Expense)/) { + return -1; + } elsif ($_[1] =~ /^Expense/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Expense)/) { + return 1; + } else { + return $_[0] cmp $_[1]; + } +} + my @sortedAccounts; -foreach my $acct ( - # Proper sorting for a chart of accounts - sort { - if ($a =~ /^Assets/ and $b !~ /^Assets/) { - return -1; - } elsif ($a =~ /^Liabilities/ and $b !~ /^Liabilitie/) { - return -1; - } else { - return $a cmp $b; - } - } @accounts) { +foreach my $acct ( sort preferredAccountSorting @accounts) { print CHART_OUTPUT "$acct\n"; push(@sortedAccounts, $acct); } diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index ce6d56da..30b27505 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -39,6 +39,36 @@ sub Commify ($) { return scalar reverse $text; } +sub preferredAccountSorting ($$) { + if ($_[0] =~ /^Assets/) { + return -1; + } elsif ($_[1] =~ /^Assets/) { + return 1; + } elsif ($_[0] =~ /^Liabilities/ and $_[1] !~ /^Assets/) { + return -1; + } elsif ($_[1] =~ /^Liabilities/ and $_[0] !~ /^Assets/) { + return 1; + } elsif ($_[0] =~ /^(Accrued)/ and $_[1] !~ /^(Assets|Liabilities)/) { + return -1; + } elsif ($_[1] =~ /^(Accrued)/ and $_[0] !~ /^(Assets|Liabilities)/) { + return 1; + } elsif ($_[0] =~ /^(Unearned Income)/ and $_[1] !~ /^(Assets|Liabilities|Accrued)/) { + return -1; + } elsif ($_[1] =~ /^(Unearned Income)/ and $_[0] !~ /^(Assets|Liabilities|Accrued)/) { + return 1; + } elsif ($_[0] =~ /^Income/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + return -1; + } elsif ($_[1] =~ /^Income/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + return 1; + } elsif ($_[0] =~ /^Expense/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Expense)/) { + return -1; + } elsif ($_[1] =~ /^Expense/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Expense)/) { + return 1; + } else { + return $_[0] cmp $_[1]; + } +} + sub ParseNumber($) { $_[0] =~ s/,//g; return Math::BigFloat->new($_[0]); -- cgit v1.2.3 From d18e01a00f88ec7d073b88db464679a9aa9910a6 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Thu, 3 Jan 2013 12:19:59 -0500 Subject: Changed balance sheet output from a TXT file to a CSV file. --- contrib/non-profit-audit-reports/summary-reports.plx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 30b27505..da2dba9d 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -146,15 +146,15 @@ foreach my $item (keys %reportFields) { print STDERR "$item: $reportFields{$item}{total}\n" if $VERBOSE; } -open(BALANCE_SHEET, ">", "balance-sheet.txt") - or die "unable to open balance-sheet.txt for writing: $!"; +open(BALANCE_SHEET, ">", "balance-sheet.csv") + or die "unable to open balance-sheet.csv for writing: $!"; -print BALANCE_SHEET " BALANCE SHEET\n", - " Ending ", $formattedEndDate, "\n", - "\n\nASSETS\n\n"; +print BALANCE_SHEET "\"BALANCE SHEET\"\n", + "\"Ending\",\"", $formattedEndDate, "\"\n", + "\n\n\"ASSETS\"\n\n"; -my $formatStr = " %-42s \$%13s\n"; -my $formatStrTotal = "%-45s \$%13s\n"; +my $formatStr = "\"\",\"%-42s\",\"\$%13s\"\n"; +my $formatStrTotal = "\"\",\"%-45s\",\"\$%13s\"\n"; my $tot = $ZERO; foreach my $item ('Cash', 'Accounts Receivable', 'Loans Receivable') { next if $reportFields{$item}{total} == $ZERO; @@ -186,7 +186,7 @@ print BALANCE_SHEET "\n", sprintf($formatStr, "TOTAL NET ASSETS", Commify($totNe close BALANCE_SHEET; print STDERR "\n"; -die "unable to write to balance-sheet.txt: $!" unless ($? == 0); +die "unable to write to balance-sheet.csv: $!" unless ($? == 0); die "Cash+accounts receivable total does not equal net assets and liabilities total" if (abs( ($reportFields{'Cash'}{total} + $reportFields{'Accounts Receivable'}{total} -- cgit v1.2.3 From b939bbe8c61a819e413b3e2a24d223f6232bd0d9 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Thu, 3 Jan 2013 12:30:43 -0500 Subject: Convert trial-balance report to CSV from TXT file. Also, ignore Ledger's Equity: accounts properly. --- .../non-profit-audit-reports/summary-reports.plx | 23 ++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index da2dba9d..dae8f2ce 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -378,15 +378,15 @@ die "calculated total of $overallTotal does equal $firstTotal" print STDERR "\n"; -open(TRIAL, ">", "trial-balance.txt") or die "unable to open accrued.txt for writing: $!"; +open(TRIAL, ">", "trial-balance.csv") or die "unable to open accrued.txt for writing: $!"; @fullCommand = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', '-e', $endDate, '-F', '%-.80A %22.108t\n', '-s', 'reg'); -print TRIAL " TRIAL BALANCE \n", - " Ending $formattedEndDate\n\n"; +print TRIAL "\"TRIAL BALANCE REPORT\",", + "\"ENDING:\",\"$formattedEndDate\"\n\n\"ACCOUNT NAME\", \"AMOUNT\"\n\n"; open(FILE, "-|", @fullCommand) or die "unable to run command ledger command: @fullCommand: $!"; @@ -395,17 +395,28 @@ print STDERR ($VERBOSE ? "Running: @fullCommand\n" : "."); my $accruedTotal = $ZERO; +my %trialBalances; + foreach my $line () { die "Unable to parse output line from second funds command: $line" unless $line =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/; my($account, $amount) = ($1, $2); $amount = ParseNumber($amount); $account =~ s/\s+$//; - next if $account =~ /\|^Equity:/ and (abs($amount) <= 0.02); - print TRIAL $line; - + next if $account =~ /\/ and (abs($amount) <= 0.02); + next if $account =~ /^Equity:/; # Stupid auto-account made by ledger. + $trialBalances{$account} = $amount; $accruedTotal += $amount; } +close FILE; +die "unable to run trial balance ledger command: $!" unless ($? == 0); + +foreach my $account (sort preferredAccountSorting keys %trialBalances) { + print TRIAL "\"$account\",\"$trialBalances{$account}\"\n"; +} + +close TRIAL; +die "unable to write trial-balance.csv: $!" unless ($? == 0); ############################################################################### # -- cgit v1.2.3 From 750321c0b1e35b0315138b35524f85d3648d8dda Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Thu, 3 Jan 2013 13:37:08 -0500 Subject: Change Income and Expenses reports to generate CSV files, rather than TXT files. --- .../non-profit-audit-reports/summary-reports.plx | 35 +++++++++++----------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index dae8f2ce..efad30e7 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -223,7 +223,7 @@ foreach my $type (keys %incomeGroups) { $incomeGroups{"OTHER"}{args} = \@otherArgs; $incomeGroups{"TOTAL"}{args} = ['/^Income/']; -open(INCOME, ">", "income.txt") or die "unable to open income.txt for writing: $!"; +open(INCOME, ">", "income.csv") or die "unable to open income.csv for writing: $!"; foreach my $type (keys %incomeGroups) { my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', @@ -247,32 +247,32 @@ foreach my $type (keys %incomeGroups) { $account =~ s/\s+$//; next if $account =~ /\/ and (abs($amount) <= 0.02); die "Weird account found, $account with amount of $amount in income command\n" - unless $account =~ s/^\s*Income://; + unless $account =~ /^\s*Income:/; $incomeGroups{$type}{total} += $amount; - $incomeGroups{$type}{output} .= " $line"; + $incomeGroups{$type}{output} .= "\"$account\",\"\$$amount\"\n"; } } -print INCOME " INCOME\n", - " Between $formattedStartDate and $formattedEndDate\n\n"; +print INCOME "\"INCOME\",", + "\"STARTING:\",\"$formattedStartDate\",\"ENDING:\",\"$formattedEndDate\"\n\n"; my $overallTotal = $ZERO; -$formatStrTotal = "%-90s \$%14s\n"; +$formatStrTotal = "\"%-90s\",\"\$%14s\"\n"; foreach my $type ('DONATIONS', 'LICENSE ENFORCEMENT', 'CONFERENCES, REGISTRATION', 'CONFERENCES, RELATED BUSINESS INCOME', 'BOOK ROYALTIES & AFFILIATE PROGRAMS', 'ADVERSITING', 'TRADEMARKS', 'INTEREST INCOME', 'OTHER') { next if ($incomeGroups{$type}{output} =~ /^\s*$/ and $incomeGroups{$type}{total} == $ZERO); - print INCOME "\n$type\n", + print INCOME "\n\"$type\"\n", $incomeGroups{$type}{output}, "\n", sprintf($formatStrTotal, "TOTAL $type:", Commify($incomeGroups{$type}{total})); $overallTotal += $incomeGroups{$type}{total}; } print INCOME "\n\n\n", sprintf($formatStrTotal, "OVERALL TOTAL:", Commify($overallTotal)); -close INCOME; die "unable to write to income.txt: $!" unless ($? == 0); +close INCOME; die "unable to write to income.csv: $!" unless ($? == 0); die "calculated total of $overallTotal does equal $incomeGroups{TOTAL}{total}" if (abs($overallTotal) - abs($incomeGroups{TOTAL}{total}) > $ONE_PENNY); @@ -298,7 +298,7 @@ foreach my $type (keys %expenseGroups, 'TRAVEL') { $expenseGroups{$type}{output} = ""; } -open(EXPENSE, ">", "expense.txt") or die "unable to open expense.txt for writing: $!"; +open(EXPENSE, ">", "expense.csv") or die "unable to open expense.csv for writing: $!"; my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', '-b', $startDate, '-e', $endDate, @@ -321,6 +321,7 @@ foreach my $line () { die "Weird account found, $account, with amount of $amount in expenses command\n" unless $account =~ /^\s*Expenses:/; + my $outputLine = "\"$account\",\"\$$amount\"\n"; my $taken = 0; # Note: Prioritize to put things under conference expenses if they were for a conference. foreach my $type ('CONFERENCES', keys %expenseGroups) { @@ -329,23 +330,23 @@ foreach my $line () { next unless $line =~ /$expenseGroups{$type}{regex}/; $taken = 1; $expenseGroups{$type}{total} += $amount; - $expenseGroups{$type}{output} .= " $line"; + $expenseGroups{$type}{output} .= $outputLine; } if (not $taken) { if ($account =~ /Travel/) { $expenseGroups{'TRAVEL'}{total} += $amount; - $expenseGroups{'TRAVEL'}{output} .= " $line"; + $expenseGroups{'TRAVEL'}{output} .= $outputLine; } else { $expenseGroups{'OTHER'}{total} += $amount; - $expenseGroups{'OTHER'}{output} .= " $line"; + $expenseGroups{'OTHER'}{output} .= $outputLine; } } $firstTotal += $amount; } -print EXPENSE " EXPENSES\n", - " Between $formattedStartDate and $formattedEndDate\n\n"; +print EXPENSE "\"EXPENSES\",", + "\"STARTING:\",\"$formattedStartDate\",\"ENDING:\",\"$formattedEndDate\"\n\n"; $overallTotal = $ZERO; -$formatStrTotal = "%-90s \$%14s\n"; +$formatStrTotal = "\"%-90s\",\"\$%14s\"\n"; my %verifyAllGroups; foreach my $key (keys %expenseGroups) { @@ -360,7 +361,7 @@ foreach my $type ('PAYROLL', 'SOFTWARE DEVELOPMENT', 'LICENSE ENFORCEMENT', 'CON die "$type is not defined!" if not defined $expenseGroups{$type}; next if ($expenseGroups{$type}{output} =~ /^\s*$/ and $expenseGroups{$type}{total} == $ZERO); - print EXPENSE "\n$type\n", + print EXPENSE "\n\"$type\"\n", $expenseGroups{$type}{output}, "\n", sprintf($formatStrTotal, "TOTAL $type:", Commify($expenseGroups{$type}{total})); $overallTotal += $expenseGroups{$type}{total}; @@ -368,7 +369,7 @@ foreach my $type ('PAYROLL', 'SOFTWARE DEVELOPMENT', 'LICENSE ENFORCEMENT', 'CON print EXPENSE "\n\n\n", sprintf($formatStrTotal, "OVERALL TOTAL:", Commify($overallTotal)); -close EXPENSE; die "unable to write to expense.txt: $!" unless ($? == 0); +close EXPENSE; die "unable to write to expense.csv: $!" unless ($? == 0); die "GROUPS NOT INCLUDED : ", join(keys(%verifyAllGroups), ", "), "\n" unless (keys %verifyAllGroups == 0); -- cgit v1.2.3 From 2fad8fe238cee8509290562a823fe0c3b312ee94 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Thu, 3 Jan 2013 13:37:18 -0500 Subject: Some minor formatting fixes for the trial balance report. --- contrib/non-profit-audit-reports/summary-reports.plx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index efad30e7..8e9339a5 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -387,7 +387,7 @@ open(TRIAL, ">", "trial-balance.csv") or die "unable to open accrued.txt for wri 'reg'); print TRIAL "\"TRIAL BALANCE REPORT\",", - "\"ENDING:\",\"$formattedEndDate\"\n\n\"ACCOUNT NAME\", \"AMOUNT\"\n\n"; + "\"ENDING:\",\"$formattedEndDate\"\n\n\"ACCOUNT NAME\",\"AMOUNT\"\n\n"; open(FILE, "-|", @fullCommand) or die "unable to run command ledger command: @fullCommand: $!"; @@ -413,7 +413,7 @@ close FILE; die "unable to run trial balance ledger command: $!" unless ($? == 0); foreach my $account (sort preferredAccountSorting keys %trialBalances) { - print TRIAL "\"$account\",\"$trialBalances{$account}\"\n"; + print TRIAL "\"$account\",\"\$$trialBalances{$account}\"\n"; } close TRIAL; -- cgit v1.2.3 From e317e1f23e236a2797b13113bc2afae14502fc5c Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 4 Jan 2013 10:18:41 -0500 Subject: Sort of accounts was buggy; it never made the final else due to bad regexes. This fix now has the sort working correctly. --- .../general-ledger-report.plx | 29 +++++++++++++--------- .../non-profit-audit-reports/summary-reports.plx | 28 ++++++++++++--------- 2 files changed, 33 insertions(+), 24 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/general-ledger-report.plx b/contrib/non-profit-audit-reports/general-ledger-report.plx index 3c9ec74d..a464053b 100755 --- a/contrib/non-profit-audit-reports/general-ledger-report.plx +++ b/contrib/non-profit-audit-reports/general-ledger-report.plx @@ -86,35 +86,40 @@ print CHART_OUTPUT "\"BEGINNING:\",\"$formattedBeginDate\","; print CHART_OUTPUT "\"ENDING:\",\"$formattedEndDate\"\n"; sub preferredAccountSorting ($$) { - if ($_[0] =~ /^Assets/) { + if ($_[0] =~ /^Assets/ and $_[1] !~ /^Assets/) { return -1; - } elsif ($_[1] =~ /^Assets/) { + } elsif ($_[1] =~ /^Assets/ and $_[0] !~ /^Assets/) { return 1; - } elsif ($_[0] =~ /^Liabilities/ and $_[1] !~ /^Assets/) { + } elsif ($_[0] =~ /^Liabilities/ and $_[1] !~ /^(Assets|Liabilities)/) { return -1; - } elsif ($_[1] =~ /^Liabilities/ and $_[0] !~ /^Assets/) { + } elsif ($_[1] =~ /^Liabilities/ and $_[0] !~ /^(Assets|Liabilities)/) { return 1; - } elsif ($_[0] =~ /^(Accrued)/ and $_[1] !~ /^(Assets|Liabilities)/) { + } elsif ($_[0] =~ /^(Accrued:[^:]+Receivable)/ and $_[1] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) { return -1; - } elsif ($_[1] =~ /^(Accrued)/ and $_[0] !~ /^(Assets|Liabilities)/) { + } elsif ($_[1] =~ /^(Accrued:[^:]+Receivable)/ and $_[0] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) { return 1; - } elsif ($_[0] =~ /^(Unearned Income)/ and $_[1] !~ /^(Assets|Liabilities|Accrued)/) { + } elsif ($_[0] =~ /^(Accrued)/ and $_[1] !~ /^(Assets|Liabilities|Accrued)/) { return -1; - } elsif ($_[1] =~ /^(Unearned Income)/ and $_[0] !~ /^(Assets|Liabilities|Accrued)/) { + } elsif ($_[1] =~ /^(Accrued)/ and $_[0] !~ /^(Assets|Liabilities|Accrued)/) { return 1; - } elsif ($_[0] =~ /^Income/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + } elsif ($_[0] =~ /^(Unearned Income)/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { return -1; - } elsif ($_[1] =~ /^Income/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + } elsif ($_[1] =~ /^(Unearned Income)/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { return 1; - } elsif ($_[0] =~ /^Expense/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Expense)/) { + } elsif ($_[0] =~ /^Income/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Income)/) { return -1; - } elsif ($_[1] =~ /^Expense/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Expense)/) { + } elsif ($_[1] =~ /^Income/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Income)/) { + return 1; + } elsif ($_[0] =~ /^Expense/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Income|Unearned Income|Expense)/) { + return -1; + } elsif ($_[1] =~ /^Expense/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Income|Unearned Income|Expense)/) { return 1; } else { return $_[0] cmp $_[1]; } } + my @sortedAccounts; foreach my $acct ( sort preferredAccountSorting @accounts) { print CHART_OUTPUT "\"$acct\"\n"; diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 8e9339a5..17999c36 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -40,29 +40,33 @@ sub Commify ($) { } sub preferredAccountSorting ($$) { - if ($_[0] =~ /^Assets/) { + if ($_[0] =~ /^Assets/ and $_[1] !~ /^Assets/) { return -1; - } elsif ($_[1] =~ /^Assets/) { + } elsif ($_[1] =~ /^Assets/ and $_[0] !~ /^Assets/) { return 1; - } elsif ($_[0] =~ /^Liabilities/ and $_[1] !~ /^Assets/) { + } elsif ($_[0] =~ /^Liabilities/ and $_[1] !~ /^(Assets|Liabilities)/) { return -1; - } elsif ($_[1] =~ /^Liabilities/ and $_[0] !~ /^Assets/) { + } elsif ($_[1] =~ /^Liabilities/ and $_[0] !~ /^(Assets|Liabilities)/) { return 1; - } elsif ($_[0] =~ /^(Accrued)/ and $_[1] !~ /^(Assets|Liabilities)/) { + } elsif ($_[0] =~ /^(Accrued:[^:]+Receivable)/ and $_[1] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) { return -1; - } elsif ($_[1] =~ /^(Accrued)/ and $_[0] !~ /^(Assets|Liabilities)/) { + } elsif ($_[1] =~ /^(Accrued:[^:]+Receivable)/ and $_[0] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) { return 1; - } elsif ($_[0] =~ /^(Unearned Income)/ and $_[1] !~ /^(Assets|Liabilities|Accrued)/) { + } elsif ($_[0] =~ /^(Accrued)/ and $_[1] !~ /^(Assets|Liabilities|Accrued)/) { return -1; - } elsif ($_[1] =~ /^(Unearned Income)/ and $_[0] !~ /^(Assets|Liabilities|Accrued)/) { + } elsif ($_[1] =~ /^(Accrued)/ and $_[0] !~ /^(Assets|Liabilities|Accrued)/) { return 1; - } elsif ($_[0] =~ /^Income/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + } elsif ($_[0] =~ /^(Unearned Income)/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { return -1; - } elsif ($_[1] =~ /^Income/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + } elsif ($_[1] =~ /^(Unearned Income)/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { return 1; - } elsif ($_[0] =~ /^Expense/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Expense)/) { + } elsif ($_[0] =~ /^Income/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Income)/) { return -1; - } elsif ($_[1] =~ /^Expense/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Expense)/) { + } elsif ($_[1] =~ /^Income/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Income)/) { + return 1; + } elsif ($_[0] =~ /^Expense/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Income|Unearned Income|Expense)/) { + return -1; + } elsif ($_[1] =~ /^Expense/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Income|Unearned Income|Expense)/) { return 1; } else { return $_[0] cmp $_[1]; -- cgit v1.2.3 From 986829b1d656611eb243b920703acc198fdf3f37 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Fri, 4 Jan 2013 10:19:16 -0500 Subject: Corrected Trial Balance report based on discussion with accountants. I believe this trial balance report will look "more natural" to accountants. --- .../non-profit-audit-reports/summary-reports.plx | 83 +++++++++++++++------- 1 file changed, 57 insertions(+), 26 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 17999c36..bbe83c88 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -385,41 +385,72 @@ print STDERR "\n"; open(TRIAL, ">", "trial-balance.csv") or die "unable to open accrued.txt for writing: $!"; -@fullCommand = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', - '-e', $endDate, - '-F', '%-.80A %22.108t\n', '-s', - 'reg'); +print TRIAL "\"TRIAL BALANCE REPORT\",\"ENDING: $formattedEndDate\"\n\n", + "\"ACCOUNT\",\"BALANCE AT $formattedStartDate\",\"CHANGE DURING FY\",\"BALANCE AT $formattedEndDate\"\n\n"; -print TRIAL "\"TRIAL BALANCE REPORT\",", - "\"ENDING:\",\"$formattedEndDate\"\n\n\"ACCOUNT NAME\",\"AMOUNT\"\n\n"; +my %commands = ( + 'totalEndFY' => [ $LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-e', $endDate, '-F', '%-.80A %22.108t\n', '-s', + 'reg' ], + 'amountInYear' => [ $LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-b', $startDate, '-e', $endDate, '-F', '%-.80A %22.108t\n', + '-s', 'reg' ], + 'totalBeginFY' => [ $LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-e', $startDate, '-F', '%-.80A %22.108t\n', + '-s', 'reg' ]); -open(FILE, "-|", @fullCommand) - or die "unable to run command ledger command: @fullCommand: $!"; +my %trialBalanceData; +my %fullAccountList; -print STDERR ($VERBOSE ? "Running: @fullCommand\n" : "."); +foreach my $id (keys %commands) { + my(@command) = @{$commands{$id}}; -my $accruedTotal = $ZERO; + open(FILE, "-|", @command) + or die "unable to run command ledger command: @command: $!"; -my %trialBalances; + print STDERR ($VERBOSE ? "Running: @command\n" : "."); -foreach my $line () { - die "Unable to parse output line from second funds command: $line" - unless $line =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/; - my($account, $amount) = ($1, $2); - $amount = ParseNumber($amount); - $account =~ s/\s+$//; - next if $account =~ /\/ and (abs($amount) <= 0.02); - next if $account =~ /^Equity:/; # Stupid auto-account made by ledger. - $trialBalances{$account} = $amount; - $accruedTotal += $amount; + foreach my $line () { + die "Unable to parse output line from trial balance $id command: $line" + unless $line =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/; + my($account, $amount) = ($1, $2); + $amount = ParseNumber($amount); + $account =~ s/\s+$//; + next if $account =~ /\/ and (abs($amount) <= 0.02); + next if $account =~ /^Equity:/; # Stupid auto-account made by ledger. + $trialBalanceData{$id}{$account} = $amount; + $fullAccountList{$account} = $id; + } + close FILE; + die "unable to run trial balance ledger command, @command: $!" unless ($? == 0); } -close FILE; -die "unable to run trial balance ledger command: $!" unless ($? == 0); -foreach my $account (sort preferredAccountSorting keys %trialBalances) { - print TRIAL "\"$account\",\"\$$trialBalances{$account}\"\n"; -} +my $curOn = 'Assets'; +foreach my $account (sort preferredAccountSorting keys %fullAccountList) { + # Blank lines right + if ($account !~ /^$curOn/) { + print TRIAL "\n"; + $curOn = $account; + $curOn =~ s/^([^:]+):.*$/$1/; + print "CurOn now: $curOn\n"; + } + if ($account =~ /^Assets|Liabilities|Accrued|Unearned Income/) { + foreach my $id (qw/totalBeginFY totalEndFY amountInYear/) { + $trialBalanceData{$id}{$account} = $ZERO + unless defined $trialBalanceData{$id}{$account}; + } + print TRIAL "\"$account\",\"\$$trialBalanceData{totalBeginFY}{$account}\",", + "\"\$$trialBalanceData{amountInYear}{$account}\",\"\$$trialBalanceData{totalEndFY}{$account}\"\n" + unless $trialBalanceData{totalBeginFY}{$account} == $ZERO and + $trialBalanceData{amountInYear}{$account} == $ZERO and + $trialBalanceData{totalEndFY}{$account} == $ZERO; + } else { + print TRIAL "\"$account\",\"\",\"\$$trialBalanceData{amountInYear}{$account}\",\"\"\n" + if defined $trialBalanceData{amountInYear}{$account} and + $trialBalanceData{amountInYear}{$account} != $ZERO; + } +} close TRIAL; die "unable to write trial-balance.csv: $!" unless ($? == 0); -- cgit v1.2.3 From 6d98bc58ae555d5d4487e7ee90c6ce8b2a49cbe6 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Sun, 6 Jan 2013 19:28:46 -0500 Subject: Correct sorting of output in trial balance report. --- contrib/non-profit-audit-reports/summary-reports.plx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index bbe83c88..e9e1a3b8 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -430,10 +430,13 @@ my $curOn = 'Assets'; foreach my $account (sort preferredAccountSorting keys %fullAccountList) { # Blank lines right if ($account !~ /^$curOn/) { - print TRIAL "\n"; + print TRIAL "pagebreak\n"; $curOn = $account; - $curOn =~ s/^([^:]+):.*$/$1/; - print "CurOn now: $curOn\n"; + if ($curOn =~ /(Accrued:[^:]+):.*$/) { + $curOn = $1; + } else { + $curOn =~ s/^([^:]+):.*$/$1/; + } } if ($account =~ /^Assets|Liabilities|Accrued|Unearned Income/) { foreach my $id (qw/totalBeginFY totalEndFY amountInYear/) { -- cgit v1.2.3 From ccd0685b6d779c520c0792e2fe6c632a60bb0362 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Sun, 27 Jan 2013 15:22:05 -0500 Subject: Correct account names due to renaming of Conference accounts in chart of accounts. --- contrib/non-profit-audit-reports/summary-reports.plx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index e9e1a3b8..5e6c447b 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -104,9 +104,9 @@ my %reportFields = 'Liabilities, Other' => {args => [ '-e', $endDate, 'bal', '/^Liabilities/', 'and', 'not', '/^Liabilities:Credit Card/']}, 'Unearned Income, Conference Registration' => {args => [ '-e', $endDate, 'bal', - '/^Unearned Income.*Conf.*Reg/' ]}, + '/^Unearned Income.*Reg/' ]}, 'Unearned Income, Other' => {args => [ '-e', $endDate, 'bal', '/^Unearned Income/', 'and', 'not', - '/^Unearned Income.*Conf.*Reg/' ]}, + '/^Unearned Income.*Reg/' ]}, 'Unrestricted Net Assets' => {args => [ '-e', $endDate, 'bal', '/^(Income|Expenses):Conservancy/' ]}, 'Temporarily Restricted Net Assets' => {args => [ '-e', $endDate, 'bal', '/^(Income|Expenses)/', 'and', 'not', '/^(Unearned Income|(Income|Expenses):Conservancy)/' ]}, @@ -213,8 +213,8 @@ my %incomeGroups = ('INTEREST INCOME' => { args => ['/^Income.*Interest/' ] }, 'DONATIONS' => { args => [ '/^Income.*Donation/' ] }, 'BOOK ROYALTIES & AFFILIATE PROGRAMS' => { args => [ '/^Income.*(Royalt|Affilate)/' ] }, - 'CONFERENCES, REGISTRATION' => {args => [ '/^Income.*Conf.*Reg/' ] }, - 'CONFERENCES, RELATED BUSINESS INCOME' => { args => [ '/^Income.*(Booth|RBI)/'] }, + 'CONFERENCES, REGISTRATION' => {args => [ '/^Income.*Reg/' ] }, + 'CONFERENCES, RELATED BUSINESS INCOME' => { args => [ '/^Income.*(Conference:.*Sponsor|Booth|RBI)/'] }, 'LICENSE ENFORCEMENT' => { args => [ '/^Income.*Enforce/' ]}, 'TRADEMARKS' => {args => [ '/^Income.*Trademark/' ]}, 'ADVERSITING' => {args => [ '/^Income.*Advertising/' ]}); -- cgit v1.2.3 From 7d04b92ad1445c60e0b61ab298f7384f0afbaa03 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Sun, 27 Jan 2013 15:22:13 -0500 Subject: This could be for any period, not just the FY. --- contrib/non-profit-audit-reports/summary-reports.plx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 5e6c447b..2860c9b4 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -386,7 +386,7 @@ print STDERR "\n"; open(TRIAL, ">", "trial-balance.csv") or die "unable to open accrued.txt for writing: $!"; print TRIAL "\"TRIAL BALANCE REPORT\",\"ENDING: $formattedEndDate\"\n\n", - "\"ACCOUNT\",\"BALANCE AT $formattedStartDate\",\"CHANGE DURING FY\",\"BALANCE AT $formattedEndDate\"\n\n"; + "\"ACCOUNT\",\"BALANCE AT $formattedStartDate\",\"CHANGE DURING PERIOD\",\"BALANCE AT $formattedEndDate\"\n\n"; my %commands = ( 'totalEndFY' => [ $LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', -- cgit v1.2.3 From cbdffb9a41b445dec9a9a99addf41112bac4f128 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Mon, 18 Feb 2013 14:36:39 -0500 Subject: Catch a few additional accounts under Conference RBI category. --- contrib/non-profit-audit-reports/summary-reports.plx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 2860c9b4..78848dc5 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -214,7 +214,7 @@ my %incomeGroups = ('INTEREST INCOME' => { args => ['/^Income.*Interest/' ] }, 'BOOK ROYALTIES & AFFILIATE PROGRAMS' => { args => [ '/^Income.*(Royalt|Affilate)/' ] }, 'CONFERENCES, REGISTRATION' => {args => [ '/^Income.*Reg/' ] }, - 'CONFERENCES, RELATED BUSINESS INCOME' => { args => [ '/^Income.*(Conference:.*Sponsor|Booth|RBI)/'] }, + 'CONFERENCES, RELATED BUSINESS INCOME' => { args => [ '/^Income.*(Conferences?:.*Sponsor|Booth|RBI)/'] }, 'LICENSE ENFORCEMENT' => { args => [ '/^Income.*Enforce/' ]}, 'TRADEMARKS' => {args => [ '/^Income.*Trademark/' ]}, 'ADVERSITING' => {args => [ '/^Income.*Advertising/' ]}); -- cgit v1.2.3 From b378359f5f4fbfad3475f362699e3d353c1f7e80 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Wed, 20 Feb 2013 07:47:33 -0500 Subject: Use a Math::BigFloat() rather than a regular float for Adjustment comparison. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on patch review by Loïc Dachary , we discovered that this script inconsistently used 0.02 as a float when comparing against numbers from the Math::BigFloat() package. While there were no known bugs related to this (presumably the 0.02 got coerced into a BigFloat (or vice-versa) and compared properly), this change nevertheless normalizes to use of a BigFloat for comparison. --- contrib/non-profit-audit-reports/summary-reports.plx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index 78848dc5..f710d765 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -80,6 +80,7 @@ sub ParseNumber($) { Math::BigFloat->precision(-2); my $ZERO = Math::BigFloat->new("0.00"); my $ONE_PENNY = Math::BigFloat->new("0.01"); +my $TWO_CENTS = Math::BigFloat->new("0.02"); if (@ARGV < 2) { print STDERR "usage: $0 \n"; @@ -249,7 +250,7 @@ foreach my $type (keys %incomeGroups) { my($account, $amount) = ($1, $2); $amount = ParseNumber($amount); $account =~ s/\s+$//; - next if $account =~ /\/ and (abs($amount) <= 0.02); + next if $account =~ /\/ and (abs($amount) <= $TWO_CENTS); die "Weird account found, $account with amount of $amount in income command\n" unless $account =~ /^\s*Income:/; @@ -321,7 +322,7 @@ foreach my $line () { my($account, $amount) = ($1, $2); $amount = ParseNumber($amount); $account =~ s/\s+$//; - next if $account =~ /\/ and (abs($amount) <= 0.02); + next if $account =~ /\/ and (abs($amount) <= $TWO_CENTS); die "Weird account found, $account, with amount of $amount in expenses command\n" unless $account =~ /^\s*Expenses:/; @@ -416,7 +417,7 @@ foreach my $id (keys %commands) { my($account, $amount) = ($1, $2); $amount = ParseNumber($amount); $account =~ s/\s+$//; - next if $account =~ /\/ and (abs($amount) <= 0.02); + next if $account =~ /\/ and (abs($amount) <= $TWO_CENTS); next if $account =~ /^Equity:/; # Stupid auto-account made by ledger. $trialBalanceData{$id}{$account} = $amount; $fullAccountList{$account} = $id; -- cgit v1.2.3 From e606dfd72f7b19b8733e7dc22cd82d8a36006298 Mon Sep 17 00:00:00 2001 From: "Bradley M. Kuhn" Date: Wed, 20 Feb 2013 07:48:30 -0500 Subject: Fix typo in error output. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HT Loïc Dachary , who noticed this. --- contrib/non-profit-audit-reports/summary-reports.plx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'contrib/non-profit-audit-reports/summary-reports.plx') diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx index f710d765..5caef4f0 100755 --- a/contrib/non-profit-audit-reports/summary-reports.plx +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -379,7 +379,7 @@ close EXPENSE; die "unable to write to expense.csv: $!" unless ($? == 0); die "GROUPS NOT INCLUDED : ", join(keys(%verifyAllGroups), ", "), "\n" unless (keys %verifyAllGroups == 0); -die "calculated total of $overallTotal does equal $firstTotal" +die "calculated total of $overallTotal does *not* equal $firstTotal" if (abs($overallTotal) - abs($firstTotal) > $ONE_PENNY); print STDERR "\n"; -- cgit v1.2.3