Merge branch 'next' into kitchen-sink
This commit is contained in:
commit
a4c81a4cd7
16 changed files with 577 additions and 145 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -103,4 +103,5 @@ contrib/non-profit-audit-reports/tests/chart-of-accounts.txt
|
||||||
contrib/non-profit-audit-reports/tests/general-ledger.csv
|
contrib/non-profit-audit-reports/tests/general-ledger.csv
|
||||||
contrib/non-profit-audit-reports/tests/general-ledger.ods
|
contrib/non-profit-audit-reports/tests/general-ledger.ods
|
||||||
contrib/non-profit-audit-reports/tests/general-ledger.txt
|
contrib/non-profit-audit-reports/tests/general-ledger.txt
|
||||||
|
contrib/non-profit-audit-reports/tests/MANIFEST
|
||||||
contrib/non-profit-audit-reports/general-ledger.zip
|
contrib/non-profit-audit-reports/general-ledger.zip
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ foreach my $acct (@accounts) {
|
||||||
my $formatString = '\n"%(date)","%C","%P","%A","%t"\n%/"","","","%A","%t"';
|
my $formatString = '\n"%(date)","%C","%P","%A","%t"\n%/"","","","%A","%t"';
|
||||||
foreach my $tagField (qw/Receipt Invoice Statement Contract PurchaseOrder Approval Check IncomeDistributionAnalysis CurrencyRate/) {
|
foreach my $tagField (qw/Receipt Invoice Statement Contract PurchaseOrder Approval Check IncomeDistributionAnalysis CurrencyRate/) {
|
||||||
print CSV_OUT ',"', $tagField, '"';
|
print CSV_OUT ',"', $tagField, '"';
|
||||||
$formatString .= ',"%(tag(\'' . $tagField . '\'))"';
|
$formatString .= ',"link:%(tag(\'' . $tagField . '\'))"';
|
||||||
}
|
}
|
||||||
$formatString .= "\n";
|
$formatString .= "\n";
|
||||||
print CSV_OUT "\n";
|
print CSV_OUT "\n";
|
||||||
|
|
@ -130,7 +130,7 @@ foreach my $acct (@accounts) {
|
||||||
open(CSV_DATA, "-|", $LEDGER_CMD, @csvRegLedgerOpts)
|
open(CSV_DATA, "-|", $LEDGER_CMD, @csvRegLedgerOpts)
|
||||||
or die "unable to run ledger command for $fileNameBase.csv: $!";
|
or die "unable to run ledger command for $fileNameBase.csv: $!";
|
||||||
|
|
||||||
while (my $line = <CSV_DATA>) { print CSV_OUT $line; }
|
while (my $line = <CSV_DATA>) { $line =~ s/"link:"/""/g; print CSV_OUT $line; }
|
||||||
close(CSV_DATA); die "Error read from csv ledger command $!" unless $? == 0;
|
close(CSV_DATA); die "Error read from csv ledger command $!" unless $? == 0;
|
||||||
|
|
||||||
SKIP_REGISTER_COMMANDS:
|
SKIP_REGISTER_COMMANDS:
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
# csv2ods.py
|
# csv2ods.py
|
||||||
# Convert example csv file to ods
|
# Convert example csv file to ods
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Tom Marble
|
# Copyright (c) 2012 Tom Marble
|
||||||
# Copyright (c) 2012 Bradley M. Kuhn
|
# Copyright (c) 2012, 2013 Bradley M. Kuhn
|
||||||
#
|
#
|
||||||
# This program gives you software freedom; you can copy, modify, convey,
|
# This program gives you software freedom; you can copy, modify, convey,
|
||||||
# and/or redistribute it under the terms of the GNU General Public License
|
# and/or redistribute it under the terms of the GNU General Public License
|
||||||
|
|
@ -24,14 +24,13 @@ import sys, os, os.path, optparse
|
||||||
import csv
|
import csv
|
||||||
import ooolib2
|
import ooolib2
|
||||||
|
|
||||||
file_fields = [ 'Receipt', 'Invoice', 'Statement', 'Contract', 'PurchaseOrder',
|
|
||||||
'Approval', 'Check', 'IncomeDistributionAnalysis', 'CurrencyRate' ]
|
|
||||||
|
|
||||||
def err(msg):
|
def err(msg):
|
||||||
print 'error: %s' % msg
|
print 'error: %s' % msg
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def csv2ods(csvname, odsname, encoding='', verbose = False):
|
def csv2ods(csvname, odsname, encoding='', verbose = False):
|
||||||
|
filesSavedinManifest = {}
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print 'converting from %s to %s' % (csvname, odsname)
|
print 'converting from %s to %s' % (csvname, odsname)
|
||||||
doc = ooolib2.Calc()
|
doc = ooolib2.Calc()
|
||||||
|
|
@ -61,25 +60,39 @@ def csv2ods(csvname, odsname, encoding='', verbose = False):
|
||||||
if len(val) > 0 and val[0] == '$':
|
if len(val) > 0 and val[0] == '$':
|
||||||
doc.set_cell_value(col + 1, row, 'currency', val[1:])
|
doc.set_cell_value(col + 1, row, 'currency', val[1:])
|
||||||
else:
|
else:
|
||||||
if ((col >= 5) and (not val in file_fields) and len(val) > 0):
|
if (len(val) > 0 and val[0:5] == "link:"):
|
||||||
|
val = val[5:]
|
||||||
linkrel = '../' + val # ../ means remove the name of the *.ods
|
linkrel = '../' + val # ../ means remove the name of the *.ods
|
||||||
linkname = os.path.basename(val) # name is just the last component
|
linkname = os.path.basename(val) # name is just the last component
|
||||||
doc.set_cell_value(col + 1, row, 'link', (linkrel, linkname))
|
doc.set_cell_value(col + 1, row, 'link', (linkrel, linkname))
|
||||||
linkpath = csvdir + '/' + val
|
linkpath = csvdir + '/' + val
|
||||||
|
|
||||||
|
if not val in filesSavedinManifest:
|
||||||
|
filesSavedinManifest[val] = col
|
||||||
|
|
||||||
|
if not os.path.exists(linkpath):
|
||||||
|
print "WARNING: link %s DOES NOT EXIST at %s" % (val, linkpath)
|
||||||
if verbose:
|
if verbose:
|
||||||
if os.path.exists(linkpath):
|
if os.path.exists(linkpath):
|
||||||
print 'relative link %s EXISTS at %s' % (val, linkpath)
|
print 'relative link %s EXISTS at %s' % (val, linkpath)
|
||||||
else:
|
|
||||||
print 'relative link %s DOES NOT EXIST at %s' % (val, linkpath)
|
|
||||||
else:
|
else:
|
||||||
doc.set_cell_value(col + 1, row, 'string', val)
|
if val == "pagebreak":
|
||||||
|
doc.sheets[doc.sheet_index].set_sheet_config(('row', row), style_pagebreak)
|
||||||
|
else:
|
||||||
|
doc.set_cell_value(col + 1, row, 'string', val)
|
||||||
else:
|
else:
|
||||||
# enter an empty string for blank lines
|
# enter an empty string for blank lines
|
||||||
doc.set_cell_value(1, row, 'string', '')
|
doc.set_cell_value(1, row, 'string', '')
|
||||||
# put a pagebreak here
|
|
||||||
doc.sheets[doc.sheet_index].set_sheet_config(('row', row), style_pagebreak)
|
|
||||||
row += 1
|
row += 1
|
||||||
# save the file
|
# save manifest file
|
||||||
|
if filesSavedinManifest.keys() != []:
|
||||||
|
manifestFH = open("MANIFEST", "a")
|
||||||
|
manifestFH.write("# Files from %s\n" % odsname)
|
||||||
|
for file in filesSavedinManifest.keys():
|
||||||
|
manifestFH.write("%s\n" % file)
|
||||||
|
|
||||||
|
manifestFH.close()
|
||||||
|
# Save spreadsheet file.
|
||||||
doc.save(odsname)
|
doc.save(odsname)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
@ -109,7 +122,7 @@ def main():
|
||||||
print 'csv:', options.csv
|
print 'csv:', options.csv
|
||||||
print 'ods:', options.ods
|
print 'ods:', options.ods
|
||||||
print 'ods:', options.encoding
|
print 'ods:', options.encoding
|
||||||
csv2ods(options.csv, options.ods, options.verbose, options.encoding)
|
csv2ods(options.csv, options.ods, options.encoding, options.verbose)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
#!/usr/bin/perl
|
#!/usr/bin/perl
|
||||||
# fund-report.plx -*- Perl -*-
|
# fund-report.plx -*- Perl -*-
|
||||||
#
|
#
|
||||||
# Script to generate a Trial Balance report for a ledger.
|
# Script to generate a Restricted Fund Report. Usefulness of this
|
||||||
|
# script may be confined to those who track separate funds in their
|
||||||
|
# accounts by having accounts that match this format:
|
||||||
|
# /^(Income|Expenses|Unearned Income|(Accrued:[^:]+:):PROJECTNAME/
|
||||||
|
|
||||||
|
# Conservancy does this because we carefully track fund balances for our
|
||||||
|
# fiscal sponsored projects. Those who aren't fiscal sponsors won't find
|
||||||
|
# this report all that useful, I suspect. Note that the name
|
||||||
|
# "Conservancy" is special-cased in a few places, mainly because our
|
||||||
|
# "General" fund is called "Conservancy".
|
||||||
|
|
||||||
#
|
#
|
||||||
# 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,
|
# This program gives you software freedom; you can copy, modify, convey,
|
||||||
# and/or redistribute it under the terms of the GNU General Public License
|
# and/or redistribute it under the terms of the GNU General Public License
|
||||||
|
|
@ -36,6 +46,7 @@ sub ParseNumber($) {
|
||||||
}
|
}
|
||||||
Math::BigFloat->precision(-2);
|
Math::BigFloat->precision(-2);
|
||||||
my $ZERO = Math::BigFloat->new("0.00");
|
my $ZERO = Math::BigFloat->new("0.00");
|
||||||
|
my $TWO_CENTS = Math::BigFloat->new("0.02");
|
||||||
|
|
||||||
if (@ARGV < 2) {
|
if (@ARGV < 2) {
|
||||||
print STDERR "usage: $0 <START_DATE> <END_DATE> <LEDGER_OPTIONS>\n";
|
print STDERR "usage: $0 <START_DATE> <END_DATE> <LEDGER_OPTIONS>\n";
|
||||||
|
|
@ -45,71 +56,65 @@ my($startDate, $endDate, @mainLedgerOptions) = @ARGV;
|
||||||
|
|
||||||
my $err;
|
my $err;
|
||||||
my $formattedEndDate = UnixDate(DateCalc(ParseDate($endDate), ParseDateDelta("- 1 day"), \$err),
|
my $formattedEndDate = UnixDate(DateCalc(ParseDate($endDate), ParseDateDelta("- 1 day"), \$err),
|
||||||
"%B %e, %Y");
|
"%Y/%m/%d");
|
||||||
die "Date calculation error on $endDate" if ($err);
|
die "Date calculation error on $endDate" if ($err);
|
||||||
my $formattedStartDate = UnixDate(ParseDate($startDate), "%B %e, %Y");
|
my $formattedStartDate = UnixDate(ParseDate($startDate), "%Y/%m/%d");
|
||||||
die "Date calculation error on $startDate" if ($err);
|
die "Date calculation error on $startDate" if ($err);
|
||||||
|
|
||||||
# First, get fund list from ending balance
|
# First, get balances for starting and ending for each fund
|
||||||
my(@ledgerOptions) = (@mainLedgerOptions,
|
|
||||||
'-V', '-X', '$', '-F', "%-.70A %22.108t\n", '-s',
|
|
||||||
'-e', $endDate, 'reg', '/^Funds:Restricted:/');
|
|
||||||
my %funds;
|
my %funds;
|
||||||
|
|
||||||
open(LEDGER_FUNDS, "-|", $LEDGER_CMD, @ledgerOptions)
|
foreach my $type ('starting', 'ending') {
|
||||||
or die "Unable to run $LEDGER_CMD for funds: $!";
|
my(@ledgerOptions) = (@mainLedgerOptions,
|
||||||
|
'-V', '-X', '$', '-F', "%-.70A %22.108t\n", '-s');
|
||||||
|
|
||||||
while (my $fundLine = <LEDGER_FUNDS>) {
|
if ($type eq 'starting') {
|
||||||
die "Unable to parse output line from first funds command: \"$fundLine\""
|
push(@ledgerOptions, '-e', $startDate);
|
||||||
unless $fundLine =~ /^\s*([^\$]+)\s+\$\s*\s*([\-\d\.\,]+)/;
|
} else {
|
||||||
my($account, $amount) = ($1, $2);
|
push(@ledgerOptions,'-e', $endDate);
|
||||||
$amount = ParseNumber($amount);
|
}
|
||||||
$account =~ s/\s+$//;
|
push(@ledgerOptions, 'reg', '/^(Income|Expenses):([^:]+):/');
|
||||||
next if $account =~ /\<Adjustment\>/ and (abs($amount) <= 0.02);
|
|
||||||
die "Weird account found, $account with amount of $amount in first funds command\n"
|
open(LEDGER_FUNDS, "-|", $LEDGER_CMD, @ledgerOptions)
|
||||||
unless $account =~ s/^\s*Funds:Restricted://;
|
or die "Unable to run $LEDGER_CMD @ledgerOptions: $!";
|
||||||
$funds{$account}{ending} = $amount;
|
|
||||||
|
while (my $fundLine = <LEDGER_FUNDS>) {
|
||||||
|
die "Unable to parse output line from first funds command: \"$fundLine\""
|
||||||
|
unless $fundLine =~ /^\s*([^\$]+)\s+\$\s*\s*([\-\d\.\,]+)/;
|
||||||
|
my($account, $amount) = ($1, $2);
|
||||||
|
$amount = ParseNumber($amount);
|
||||||
|
$account =~ s/\s+$//;
|
||||||
|
next if $account =~ /\<Adjustment\>/ and (abs($amount) <= $TWO_CENTS);
|
||||||
|
die "Weird account found, $account with amount of $amount in command: @ledgerOptions\n"
|
||||||
|
unless $account =~ s/^\s*(?:Income|Expenses):([^:]+)://;
|
||||||
|
$account = $1;
|
||||||
|
$account = 'General' if $account eq 'Conservancy'; # FIXME: this is a special case for Consrevancy
|
||||||
|
$funds{$account}{$type} += $amount;
|
||||||
|
}
|
||||||
|
close LEDGER_FUNDS;
|
||||||
|
die "Failure on ledger command @ledgerOptions: $!" unless ($? == 0);
|
||||||
}
|
}
|
||||||
close LEDGER_FUNDS;
|
|
||||||
|
|
||||||
# First, get fund list from starting balance
|
|
||||||
@ledgerOptions = (@mainLedgerOptions,
|
|
||||||
'-V', '-X', '$', '-F', "%-.70A %22.108t\n", '-w', '-s',
|
|
||||||
'-e', $startDate, 'reg', '^Funds:Restricted:');
|
|
||||||
|
|
||||||
open(LEDGER_FUNDS, "-|", $LEDGER_CMD, @ledgerOptions)
|
|
||||||
or die "Unable to run $LEDGER_CMD for funds: $!";
|
|
||||||
|
|
||||||
while (my $fundLine = <LEDGER_FUNDS>) {
|
|
||||||
die "Unable to parse output line from second funds command: $fundLine"
|
|
||||||
unless $fundLine =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/;
|
|
||||||
my($account, $amount) = ($1, $2);
|
|
||||||
$amount = ParseNumber($amount);
|
|
||||||
$account =~ s/\s+$//;
|
|
||||||
next if $account =~ /\<Adjustment\>/ and (abs($amount) <= 0.02);
|
|
||||||
die "Weird account found, $account with amount of $amount in first second command\n"
|
|
||||||
unless $account =~ s/^\s*Funds:Restricted://;
|
|
||||||
$funds{$account}{starting} = $amount;
|
|
||||||
}
|
|
||||||
close LEDGER_FUNDS;
|
|
||||||
|
|
||||||
|
|
||||||
foreach my $fund (keys %funds) {
|
foreach my $fund (keys %funds) {
|
||||||
$funds{$fund}{starting} = $ZERO if not defined $funds{$fund}{starting};
|
foreach my $type (keys %{$funds{$fund}}) {
|
||||||
|
$funds{$fund}{$type} = $ZERO - $funds{$fund}{$type};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
my(@ledgerOptions) = (@mainLedgerOptions,
|
||||||
@ledgerOptions = (@mainLedgerOptions,
|
|
||||||
'-V', '-X', '$', '-F', "%-.70A %22.108t\n", '-w', '-s',
|
'-V', '-X', '$', '-F', "%-.70A %22.108t\n", '-w', '-s',
|
||||||
'-b', $startDate, '-e', $endDate, 'reg');
|
'-b', $startDate, '-e', $endDate, 'reg');
|
||||||
|
|
||||||
my @possibleTypes = ('Unearned Income', 'Retained Earnings', 'Retained Costs',
|
my @possibleTypes = ('Income', 'Expenses', 'Unearned Income', 'Retained Earnings', 'Retained Costs',
|
||||||
'Accrued:Accounts Payable', 'Accrued:Accounts Receivable');
|
'Accrued:Loans Receivable', 'Accrued:Accounts Payable',
|
||||||
|
'Accrued:Accounts Receivable', 'Accrued:Expenses');
|
||||||
|
|
||||||
foreach my $type ('Income', 'Expenses', @possibleTypes) {
|
foreach my $type (@possibleTypes) {
|
||||||
foreach my $fund (keys %funds) {
|
foreach my $fund (keys %funds) {
|
||||||
open(LEDGER_INCOME, "-|", $LEDGER_CMD, @ledgerOptions, "^${type}:$fund")
|
my $query;
|
||||||
|
$query = ($fund eq 'General') ? "/^${type}:Conservancy/": "/^${type}:$fund/";
|
||||||
|
open(LEDGER_INCOME, "-|", $LEDGER_CMD, @ledgerOptions, $query)
|
||||||
or die "Unable to run $LEDGER_CMD for funds: $!";
|
or die "Unable to run $LEDGER_CMD for funds: $!";
|
||||||
$funds{$fund}{$type} = $ZERO;
|
$funds{$fund}{$type} = $ZERO;
|
||||||
while (my $line = <LEDGER_INCOME>) {
|
while (my $line = <LEDGER_INCOME>) {
|
||||||
die "Unable to parse output line from $type line command: $line"
|
die "Unable to parse output line from $type line command: $line"
|
||||||
unless $line =~ /^\s*([^\$]+)\s+\$\s*\s*([\-\d\.\,]+)/;
|
unless $line =~ /^\s*([^\$]+)\s+\$\s*\s*([\-\d\.\,]+)/;
|
||||||
|
|
@ -118,34 +123,110 @@ foreach my $type ('Income', 'Expenses', @possibleTypes) {
|
||||||
$funds{$fund}{$type} += $amount;
|
$funds{$fund}{$type} += $amount;
|
||||||
}
|
}
|
||||||
close LEDGER_INCOME;
|
close LEDGER_INCOME;
|
||||||
|
die "Failure on ledger command for ${type}:$fund: $!" unless ($? == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my($totStart, $totEnd) = ($ZERO, $ZERO);
|
my %tot;
|
||||||
|
($tot{Start}, $tot{End}) = ($ZERO, $ZERO);
|
||||||
|
|
||||||
foreach my $fund (sort keys %funds) {
|
my %beforeEndings = ('Income' => 1, 'Expenses' => 1);
|
||||||
|
my %afterEndings;
|
||||||
|
|
||||||
|
# For other @possibleTypes, build up @fieldsList to just thoes that are present.
|
||||||
|
|
||||||
|
foreach my $fund (keys %funds) {
|
||||||
|
foreach my $type (@possibleTypes) {
|
||||||
|
if ($funds{$fund}{$type} != $ZERO) {
|
||||||
|
if ($type =~ /^(Unearned Income|Accrued)/) {
|
||||||
|
$afterEndings{$type} = 1;
|
||||||
|
} else {
|
||||||
|
$beforeEndings{$type} = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
my(@beforeEndingFields, @afterEndingFields);
|
||||||
|
|
||||||
|
foreach my $ii (@possibleTypes) {
|
||||||
|
push(@beforeEndingFields, $ii) if defined $beforeEndings{$ii};
|
||||||
|
push(@afterEndingFields, $ii) if defined $afterEndings{$ii};
|
||||||
|
}
|
||||||
|
# Make sure fieldLists present items are zero for those that should be zero.
|
||||||
|
foreach my $fund (keys %funds) {
|
||||||
|
foreach my $type ('starting', @beforeEndingFields, 'ending', @afterEndingFields) {
|
||||||
|
$funds{$fund}{$type} = $ZERO unless defined $funds{$fund}{$type};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print '"RESTRICTED AND GENERAL FUND REPORT",', "\"BEGINNING:\",\"$formattedStartDate\",\"ENDING:\",\"$formattedEndDate\"\n\n";
|
||||||
|
print '"FUND","STARTING BALANCE",';
|
||||||
|
my @finalPrints;
|
||||||
|
foreach my $type (@beforeEndingFields) {
|
||||||
|
$tot{$type} = $ZERO;
|
||||||
|
my $formattedType = $type;
|
||||||
|
print "\"$formattedType\",";
|
||||||
|
}
|
||||||
|
print '"ENDING BALANCE",""';
|
||||||
|
foreach my $type (@afterEndingFields) {
|
||||||
|
$tot{$type} = $ZERO;
|
||||||
|
my $formattedType = $type;
|
||||||
|
$formattedType = "Prepaid Expenses" if $formattedType eq 'Accrued:Expenses';
|
||||||
|
$formattedType =~ s/^Accrued://;
|
||||||
|
print ",\"$formattedType\"";
|
||||||
|
}
|
||||||
|
print "\n\n";
|
||||||
|
|
||||||
|
sub printTotal ($$) {
|
||||||
|
my($label, $tot) = @_;
|
||||||
|
print "\"$label\",\"\$$tot->{Start}\",";
|
||||||
|
foreach my $type (@beforeEndingFields) {
|
||||||
|
print "\"\$$tot->{$type}\",";
|
||||||
|
}
|
||||||
|
print "\"\$$tot->{End}\",\"\"";
|
||||||
|
foreach my $type (@afterEndingFields) {
|
||||||
|
print ",\"\$$tot->{$type}\"";
|
||||||
|
}
|
||||||
|
print "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $fund (sort {
|
||||||
|
if ($a eq "General") { return 1 }
|
||||||
|
elsif ($b eq "General") { return -1 }
|
||||||
|
else { return $a cmp $b } }
|
||||||
|
keys %funds) {
|
||||||
my $sanityTotal = $funds{$fund}{starting};
|
my $sanityTotal = $funds{$fund}{starting};
|
||||||
print "Fund: $fund\n", sprintf("%-35s\$%26.2f\n\n", "Balance as of $formattedStartDate:",
|
|
||||||
$funds{$fund}{starting});
|
if ($fund eq 'General') {
|
||||||
foreach my $type ('Income', 'Expenses', @possibleTypes) {
|
print "\n";
|
||||||
my $formattedType = $type; $formattedType =~ s/^Accrued://;
|
printTotal("Restricted Subtotal", \%tot);
|
||||||
next if $type ne 'Income' and $type ne 'Expenses' and $funds{$fund}{$type} == $ZERO;
|
print "\n";
|
||||||
print sprintf("%19s during period: \$%26.2f\n", $formattedType, $funds{$fund}{$type});
|
|
||||||
}
|
}
|
||||||
print sprintf("\n%-35s\$%26.2f\n", "Balance as of $formattedEndDate:",
|
$tot{Start} += $funds{$fund}{starting};
|
||||||
$funds{$fund}{ending}), "\n\n";
|
$tot{End} += $funds{$fund}{ending};
|
||||||
|
|
||||||
|
print "\"$fund\",\"\$$funds{$fund}{starting}\",";
|
||||||
|
foreach my $type (@beforeEndingFields) {
|
||||||
|
print "\"\$$funds{$fund}{$type}\",";
|
||||||
|
$tot{$type} += $funds{$fund}{$type};
|
||||||
|
}
|
||||||
|
print "\"\$$funds{$fund}{ending}\",\"\"";
|
||||||
|
foreach my $type (@afterEndingFields) {
|
||||||
|
print ",\"\$$funds{$fund}{$type}\"";
|
||||||
|
$tot{$type} += $funds{$fund}{$type};
|
||||||
|
}
|
||||||
|
print "\n";
|
||||||
# Santity check:
|
# Santity check:
|
||||||
if ($funds{$fund}{ending} !=
|
if (abs($funds{$fund}{ending} -
|
||||||
($funds{$fund}{starting}
|
($funds{$fund}{starting}
|
||||||
- $funds{$fund}{Income} - $funds{$fund}{'Unearned Income'} - $funds{$fund}{Expenses})) {
|
- $funds{$fund}{Income} - $funds{$fund}{Expenses}))
|
||||||
print "$fund FAILED SANITY CHECK\n\n\n";
|
> $TWO_CENTS) {
|
||||||
die "$fund FAILED SANITY CHECK";
|
print "$fund FAILED SANITY CHECK: Ending: $funds{$fund}{ending} \n\n\n";
|
||||||
|
warn "$fund FAILED SANITY CHECK";
|
||||||
}
|
}
|
||||||
$totStart += $funds{$fund}{starting};
|
|
||||||
$totEnd += $funds{$fund}{ending};
|
|
||||||
}
|
}
|
||||||
print "\n\n\nTotal Restricted Funds as of $formattedStartDate: ", sprintf("\$%15.2f\n", $totStart);
|
print "\n";
|
||||||
print "\nTotal Restricted Funds as of $formattedStartDate: ", sprintf("\$%15.2f\n", $totEnd);
|
printTotal("OVERALL TOTAL", \%tot);
|
||||||
###############################################################################
|
###############################################################################
|
||||||
#
|
#
|
||||||
# Local variables:
|
# Local variables:
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
# Script to generate a General Ledger report that accountants like
|
# Script to generate a General Ledger report that accountants like
|
||||||
# using Ledger.
|
# using Ledger.
|
||||||
#
|
#
|
||||||
# Copyright (C) 2011, 2012 Bradley M. Kuhn
|
# Copyright (C) 2011, 2012, 2013 Bradley M. Kuhn
|
||||||
# Copyright (C) 2012 Tom Marble
|
# Copyright (C) 2012 Tom Marble
|
||||||
#
|
#
|
||||||
# This program gives you software freedom; you can copy, modify, convey,
|
# This program gives you software freedom; you can copy, modify, convey,
|
||||||
# and/or redistribute it under the terms of the GNU General Public License
|
# and/or redistribute it under the terms of the GNU General Public License
|
||||||
|
|
@ -44,10 +44,24 @@ if (@ARGV < 3) {
|
||||||
print STDERR "usage: $0 <BEGIN_DATE> <END_DATE> <OTHER_LEDGER_OPTS>\n";
|
print STDERR "usage: $0 <BEGIN_DATE> <END_DATE> <OTHER_LEDGER_OPTS>\n";
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
open(MANIFEST, ">", "MANIFEST") or die "Unable to open MANIFEST for writing: $!";
|
open(MANIFEST, ">", "MANIFEST") or die "Unable to open MANIFEST for writing: $!";
|
||||||
|
|
||||||
my($beginDate, $endDate, @otherLedgerOpts) = @ARGV;
|
my($beginDate, $endDate, @otherLedgerOpts) = @ARGV;
|
||||||
|
|
||||||
|
my $formattedEndDate = new Date::Manip::Date;
|
||||||
|
die "badly formatted end date, $endDate" if $formattedEndDate->parse($endDate);
|
||||||
|
my $oneDayLess = new Date::Manip::Delta;
|
||||||
|
die "bad one day less" if $oneDayLess->parse("- 1 day");
|
||||||
|
$formattedEndDate = $formattedEndDate->calc($oneDayLess);
|
||||||
|
$formattedEndDate = $formattedEndDate->printf("%Y/%m/%d");
|
||||||
|
|
||||||
|
my $formattedBeginDate = new Date::Manip::Date;
|
||||||
|
die "badly formatted end date, $endDate" if $formattedBeginDate->parse($endDate);
|
||||||
|
$formattedBeginDate = $formattedBeginDate->printf("%Y/%m/%d");
|
||||||
|
|
||||||
|
|
||||||
my(@chartOfAccountsOpts) = ('-V', '-F', "%150A\n", '-w', '-s',
|
my(@chartOfAccountsOpts) = ('-V', '-F', "%150A\n", '-w', '-s',
|
||||||
'-b', $beginDate, '-e', $endDate, @otherLedgerOpts, 'reg');
|
'-b', $beginDate, '-e', $endDate, @otherLedgerOpts, 'reg');
|
||||||
|
|
||||||
|
|
@ -64,32 +78,83 @@ while (my $line = <CHART_DATA>) {
|
||||||
}
|
}
|
||||||
close(CHART_DATA); die "error reading ledger output for chart of accounts: $!" unless $? == 0;
|
close(CHART_DATA); die "error reading ledger output for chart of accounts: $!" unless $? == 0;
|
||||||
|
|
||||||
open(CHART_OUTPUT, ">", "chart-of-accounts.txt") or die "unable to write chart-of-accounts.txt: $!";
|
open(CHART_OUTPUT, ">", "chart-of-accounts.csv") or die "unable to write chart-of-accounts.csv: $!";
|
||||||
print MANIFEST "chart-of-accounts.txt\n";
|
print MANIFEST "chart-of-accounts.csv\n";
|
||||||
|
|
||||||
|
print CHART_OUTPUT "\"CHART OF ACCOUNTS\",";
|
||||||
|
print CHART_OUTPUT "\"BEGINNING:\",\"$formattedBeginDate\",";
|
||||||
|
print CHART_OUTPUT "\"ENDING:\",\"$formattedEndDate\"\n";
|
||||||
|
|
||||||
|
sub preferredAccountSorting ($$) {
|
||||||
|
if ($_[0] =~ /^Assets/ and $_[1] !~ /^Assets/) {
|
||||||
|
return -1;
|
||||||
|
} elsif ($_[1] =~ /^Assets/ and $_[0] !~ /^Assets/) {
|
||||||
|
return 1;
|
||||||
|
} elsif ($_[0] =~ /^Liabilities/ and $_[1] !~ /^(Assets|Liabilities)/) {
|
||||||
|
return -1;
|
||||||
|
} elsif ($_[1] =~ /^Liabilities/ and $_[0] !~ /^(Assets|Liabilities)/) {
|
||||||
|
return 1;
|
||||||
|
} elsif ($_[0] =~ /^(Accrued:[^:]+Receivable)/ and $_[1] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) {
|
||||||
|
return -1;
|
||||||
|
} elsif ($_[1] =~ /^(Accrued:[^:]+Receivable)/ and $_[0] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) {
|
||||||
|
return 1;
|
||||||
|
} elsif ($_[0] =~ /^(Accrued)/ and $_[1] !~ /^(Assets|Liabilities|Accrued)/) {
|
||||||
|
return -1;
|
||||||
|
} elsif ($_[1] =~ /^(Accrued)/ and $_[0] !~ /^(Assets|Liabilities|Accrued)/) {
|
||||||
|
return 1;
|
||||||
|
} elsif ($_[0] =~ /^(Unearned Income)/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) {
|
||||||
|
return -1;
|
||||||
|
} elsif ($_[1] =~ /^(Unearned Income)/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) {
|
||||||
|
return 1;
|
||||||
|
} elsif ($_[0] =~ /^Income/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Income)/) {
|
||||||
|
return -1;
|
||||||
|
} 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;
|
my @sortedAccounts;
|
||||||
foreach my $acct (
|
foreach my $acct ( sort preferredAccountSorting @accounts) {
|
||||||
# Proper sorting for a chart of accounts
|
print CHART_OUTPUT "\"$acct\"\n";
|
||||||
sort {
|
|
||||||
if ($a =~ /^Assets/ and $b !~ /^Assets/) {
|
|
||||||
return -1;
|
|
||||||
} elsif ($a =~ /^Liabilities/ and $b !~ /^Liabilitie/) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return $a cmp $b;
|
|
||||||
}
|
|
||||||
} @accounts) {
|
|
||||||
print CHART_OUTPUT "$acct\n";
|
|
||||||
push(@sortedAccounts, $acct);
|
push(@sortedAccounts, $acct);
|
||||||
}
|
}
|
||||||
close(CHART_OUTPUT); die "error writing to chart-of-accounts.txt: $!" unless $? == 0;
|
close(CHART_OUTPUT); die "error writing to chart-of-accounts.txt: $!" unless $? == 0;
|
||||||
|
|
||||||
my $formattedEndDate = new Date::Manip::Date;
|
my %commands = (
|
||||||
die "badly formatted end date, $endDate" if $formattedEndDate->parse($endDate);
|
'totalEnd' => [ $LEDGER_CMD, @otherLedgerOpts, '-V', '-X', '$',
|
||||||
my $oneDayLess = new Date::Manip::Delta;
|
'-e', $endDate, '-F', '%-.80A %22.108t\n', '-s',
|
||||||
die "bad one day less" if $oneDayLess->parse("- 1 day");
|
'reg' ],
|
||||||
$formattedEndDate = $formattedEndDate->calc($oneDayLess);
|
'totalBegin' => [ $LEDGER_CMD, @otherLedgerOpts, '-V', '-X', '$',
|
||||||
$formattedEndDate = $formattedEndDate->printf("%Y/%m/%d");
|
'-e', $beginDate, '-F', '%-.80A %22.108t\n',
|
||||||
|
'-s', 'reg' ]);
|
||||||
|
|
||||||
|
my %balanceData;
|
||||||
|
|
||||||
|
foreach my $id (keys %commands) {
|
||||||
|
my(@command) = @{$commands{$id}};
|
||||||
|
|
||||||
|
open(FILE, "-|", @command) or die "unable to run command ledger command: @command: $!";
|
||||||
|
|
||||||
|
foreach my $line (<FILE>) {
|
||||||
|
die "Unable to parse output line from balance data $id command: $line"
|
||||||
|
unless $line =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/;
|
||||||
|
my($account, $amount) = ($1, $2);
|
||||||
|
$amount = ParseNumber($amount);
|
||||||
|
$account =~ s/\s+$//;
|
||||||
|
next if $account =~ /\<Adjustment\>/ and (abs($amount) <= 0.02);
|
||||||
|
next if $account =~ /^Equity:/; # Stupid auto-account made by ledger.
|
||||||
|
$balanceData{$id}{$account} = $amount;
|
||||||
|
}
|
||||||
|
close FILE;
|
||||||
|
die "unable to run balance data ledger command, @command: $!" unless ($? == 0);
|
||||||
|
}
|
||||||
|
|
||||||
open(GL_TEXT_OUT, ">", "general-ledger.txt") or die "unable to write general-ledger.txt: $!";
|
open(GL_TEXT_OUT, ">", "general-ledger.txt") or die "unable to write general-ledger.txt: $!";
|
||||||
print MANIFEST "general-ledger.txt\n";
|
print MANIFEST "general-ledger.txt\n";
|
||||||
|
|
@ -110,32 +175,44 @@ foreach my $acct (@sortedAccounts) {
|
||||||
}
|
}
|
||||||
close(GL_TEXT_DATA); die "error reading ledger output for chart of accounts: $!" unless $? == 0;
|
close(GL_TEXT_DATA); die "error reading ledger output for chart of accounts: $!" unless $? == 0;
|
||||||
|
|
||||||
print GL_CSV_OUT "\n\"ACCOUNT:\",\"$acct\"\n\"PERIOD START:\",\"$beginDate\"\n\"PERIOD END:\",\"$formattedEndDate\"\n";
|
print GL_CSV_OUT "\n\"ACCOUNT:\",\"$acct\"\n\"PERIOD START:\",\"$formattedBeginDate\"\n\"PERIOD END:\",\"$formattedEndDate\"\n";
|
||||||
print GL_CSV_OUT '"DATE","CHECK NUM","NAME","TRANSACTION AMT","RUNNING TOTAL"';
|
print GL_CSV_OUT '"DATE","CHECK NUM","NAME","TRANSACTION AMT","BALANCE"';
|
||||||
my $formatString = '"%(date)","%C","%P","%t","%T"';
|
|
||||||
|
my $formatString = '"%(date)","%C","%P","%t",""';
|
||||||
foreach my $tagField (qw/Receipt Invoice Statement Contract PurchaseOrder Approval Check IncomeDistributionAnalysis CurrencyRate/) {
|
foreach my $tagField (qw/Receipt Invoice Statement Contract PurchaseOrder Approval Check IncomeDistributionAnalysis CurrencyRate/) {
|
||||||
print GL_CSV_OUT ',"', $tagField, '"';
|
print GL_CSV_OUT ',"', $tagField, '"';
|
||||||
$formatString .= ',"%(tag(\'' . $tagField . '\'))"';
|
$formatString .= ',"link:%(tag(\'' . $tagField . '\'))"';
|
||||||
}
|
}
|
||||||
$formatString .= "\n";
|
$formatString .= "\n";
|
||||||
print GL_CSV_OUT "\n";
|
print GL_CSV_OUT "\n";
|
||||||
|
if ($acct =~ /^(Assets|Liabilities|Accrued|Unearned Income)/) {
|
||||||
|
$balanceData{totalBegin}{$acct} = $ZERO unless defined $balanceData{totalBegin}{$acct};
|
||||||
|
print GL_CSV_OUT "\"$formattedBeginDate\"", ',"","BALANCE","","$', "$balanceData{totalBegin}{$acct}\"\n";
|
||||||
|
}
|
||||||
|
|
||||||
@acctLedgerOpts = ('-V', '-F', $formatString, '-w', '--sort', 'd', '-b', $beginDate, '-e', $endDate, @otherLedgerOpts, 'reg', $acct);
|
@acctLedgerOpts = ('-V', '-F', $formatString, '-w', '--sort', 'd', '-b', $beginDate, '-e', $endDate, @otherLedgerOpts, 'reg', $acct);
|
||||||
open(GL_CSV_DATA, "-|", $LEDGER_CMD, @acctLedgerOpts)
|
open(GL_CSV_DATA, "-|", $LEDGER_CMD, @acctLedgerOpts)
|
||||||
or die "Unable to run $LEDGER_CMD @acctLedgerOpts: $!";
|
or die "Unable to run $LEDGER_CMD @acctLedgerOpts: $!";
|
||||||
|
|
||||||
foreach my $line (<GL_CSV_DATA>) {
|
foreach my $line (<GL_CSV_DATA>) {
|
||||||
|
$line =~ s/"link:"/""/g;
|
||||||
print GL_CSV_OUT $line;
|
print GL_CSV_OUT $line;
|
||||||
next if $line =~ /ACCOUNT:.*PERIOD/; # Skip column header lines
|
next if $line =~ /ACCOUNT:.*PERIOD/; # Skip column header lines
|
||||||
$line =~ s/^"[^"]*","[^"]*","[^"]*","[^"]*","[^"]*",//;
|
$line =~ s/^"[^"]*","[^"]*","[^"]*","[^"]*","[^"]*",//;
|
||||||
while ($line =~ s/^"([^"]*)"(,|$)//) {
|
while ($line =~ s/^"([^"]*)"(,|$)//) {
|
||||||
my $file = $1;
|
my $file = $1;
|
||||||
next if $file =~ /^\s*$/;
|
next if $file =~ /^\s*$/;
|
||||||
|
$file =~ s/^link:(.*)$/$1/;
|
||||||
warn "$file does not exist and/or is not readable" unless -r $file;
|
warn "$file does not exist and/or is not readable" unless -r $file;
|
||||||
print MANIFEST "$file\n" if not defined $manifest{$file};
|
print MANIFEST "$file\n" if not defined $manifest{$file};
|
||||||
$manifest{$file} = $line;
|
$manifest{$file} = $line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($acct =~ /^(Assets|Liabilities|Accrued|Unearned Income)/) {
|
||||||
|
$balanceData{totalEnd}{$acct} = $ZERO unless defined $balanceData{totalEnd}{$acct};
|
||||||
|
print GL_CSV_OUT "\"$formattedEndDate\"", ',"","BALANCE","","$', "$balanceData{totalEnd}{$acct}\"\n";
|
||||||
|
}
|
||||||
|
print GL_CSV_OUT "pagebreak\n";
|
||||||
close(GL_CSV_DATA); die "error reading ledger output for chart of accounts: $!" unless $? == 0;
|
close(GL_CSV_DATA); die "error reading ledger output for chart of accounts: $!" unless $? == 0;
|
||||||
}
|
}
|
||||||
close(GL_TEXT_OUT); die "error writing to general-ledger.txt: $!" unless $? == 0;
|
close(GL_TEXT_OUT); die "error writing to general-ledger.txt: $!" unless $? == 0;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#
|
#
|
||||||
# Script to generate end-of-year summary reports.
|
# 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,
|
# This program gives you software freedom; you can copy, modify, convey,
|
||||||
# and/or redistribute it under the terms of the GNU General Public License
|
# and/or redistribute it under the terms of the GNU General Public License
|
||||||
|
|
@ -39,6 +39,40 @@ sub Commify ($) {
|
||||||
return scalar reverse $text;
|
return scalar reverse $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub preferredAccountSorting ($$) {
|
||||||
|
if ($_[0] =~ /^Assets/ and $_[1] !~ /^Assets/) {
|
||||||
|
return -1;
|
||||||
|
} elsif ($_[1] =~ /^Assets/ and $_[0] !~ /^Assets/) {
|
||||||
|
return 1;
|
||||||
|
} elsif ($_[0] =~ /^Liabilities/ and $_[1] !~ /^(Assets|Liabilities)/) {
|
||||||
|
return -1;
|
||||||
|
} elsif ($_[1] =~ /^Liabilities/ and $_[0] !~ /^(Assets|Liabilities)/) {
|
||||||
|
return 1;
|
||||||
|
} elsif ($_[0] =~ /^(Accrued:[^:]+Receivable)/ and $_[1] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) {
|
||||||
|
return -1;
|
||||||
|
} elsif ($_[1] =~ /^(Accrued:[^:]+Receivable)/ and $_[0] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) {
|
||||||
|
return 1;
|
||||||
|
} elsif ($_[0] =~ /^(Accrued)/ and $_[1] !~ /^(Assets|Liabilities|Accrued)/) {
|
||||||
|
return -1;
|
||||||
|
} elsif ($_[1] =~ /^(Accrued)/ and $_[0] !~ /^(Assets|Liabilities|Accrued)/) {
|
||||||
|
return 1;
|
||||||
|
} elsif ($_[0] =~ /^(Unearned Income)/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) {
|
||||||
|
return -1;
|
||||||
|
} elsif ($_[1] =~ /^(Unearned Income)/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) {
|
||||||
|
return 1;
|
||||||
|
} elsif ($_[0] =~ /^Income/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Income)/) {
|
||||||
|
return -1;
|
||||||
|
} 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub ParseNumber($) {
|
sub ParseNumber($) {
|
||||||
$_[0] =~ s/,//g;
|
$_[0] =~ s/,//g;
|
||||||
return Math::BigFloat->new($_[0]);
|
return Math::BigFloat->new($_[0]);
|
||||||
|
|
@ -116,15 +150,15 @@ foreach my $item (keys %reportFields) {
|
||||||
print STDERR "$item: $reportFields{$item}{total}\n" if $VERBOSE;
|
print STDERR "$item: $reportFields{$item}{total}\n" if $VERBOSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
open(BALANCE_SHEET, ">", "balance-sheet.txt")
|
open(BALANCE_SHEET, ">", "balance-sheet.csv")
|
||||||
or die "unable to open balance-sheet.txt for writing: $!";
|
or die "unable to open balance-sheet.csv for writing: $!";
|
||||||
|
|
||||||
print BALANCE_SHEET " BALANCE SHEET\n",
|
print BALANCE_SHEET "\"BALANCE SHEET\"\n",
|
||||||
" Ending ", $formattedEndDate, "\n",
|
"\"Ending\",\"", $formattedEndDate, "\"\n",
|
||||||
"\n\nASSETS\n\n";
|
"\n\n\"ASSETS\"\n\n";
|
||||||
|
|
||||||
my $formatStr = " %-42s \$%13s\n";
|
my $formatStr = "\"\",\"%-42s\",\"\$%13s\"\n";
|
||||||
my $formatStrTotal = "%-45s \$%13s\n";
|
my $formatStrTotal = "\"\",\"%-45s\",\"\$%13s\"\n";
|
||||||
my $tot = $ZERO;
|
my $tot = $ZERO;
|
||||||
foreach my $item ('Cash', 'Accounts Receivable', 'Loans Receivable') {
|
foreach my $item ('Cash', 'Accounts Receivable', 'Loans Receivable') {
|
||||||
next if $reportFields{$item}{total} == $ZERO;
|
next if $reportFields{$item}{total} == $ZERO;
|
||||||
|
|
@ -156,7 +190,7 @@ print BALANCE_SHEET "\n", sprintf($formatStr, "TOTAL NET ASSETS", Commify($totNe
|
||||||
|
|
||||||
close BALANCE_SHEET;
|
close BALANCE_SHEET;
|
||||||
print STDERR "\n";
|
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"
|
die "Cash+accounts receivable total does not equal net assets and liabilities total"
|
||||||
if (abs( ($reportFields{'Cash'}{total} + $reportFields{'Accounts Receivable'}{total}
|
if (abs( ($reportFields{'Cash'}{total} + $reportFields{'Accounts Receivable'}{total}
|
||||||
|
|
@ -193,7 +227,7 @@ foreach my $type (keys %incomeGroups) {
|
||||||
$incomeGroups{"OTHER"}{args} = \@otherArgs;
|
$incomeGroups{"OTHER"}{args} = \@otherArgs;
|
||||||
$incomeGroups{"TOTAL"}{args} = ['/^Income/'];
|
$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) {
|
foreach my $type (keys %incomeGroups) {
|
||||||
my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$',
|
my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$',
|
||||||
|
|
@ -217,32 +251,32 @@ foreach my $type (keys %incomeGroups) {
|
||||||
$account =~ s/\s+$//;
|
$account =~ s/\s+$//;
|
||||||
next if $account =~ /\<Adjustment\>/ and (abs($amount) <= 0.02);
|
next if $account =~ /\<Adjustment\>/ and (abs($amount) <= 0.02);
|
||||||
die "Weird account found, $account with amount of $amount in income command\n"
|
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}{total} += $amount;
|
||||||
$incomeGroups{$type}{output} .= " $line";
|
$incomeGroups{$type}{output} .= "\"$account\",\"\$$amount\"\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print INCOME " INCOME\n",
|
print INCOME "\"INCOME\",",
|
||||||
" Between $formattedStartDate and $formattedEndDate\n\n";
|
"\"STARTING:\",\"$formattedStartDate\",\"ENDING:\",\"$formattedEndDate\"\n\n";
|
||||||
|
|
||||||
|
|
||||||
my $overallTotal = $ZERO;
|
my $overallTotal = $ZERO;
|
||||||
|
|
||||||
$formatStrTotal = "%-90s \$%14s\n";
|
$formatStrTotal = "\"%-90s\",\"\$%14s\"\n";
|
||||||
foreach my $type ('DONATIONS', 'LICENSE ENFORCEMENT',
|
foreach my $type ('DONATIONS', 'LICENSE ENFORCEMENT',
|
||||||
'CONFERENCES, REGISTRATION', 'CONFERENCES, RELATED BUSINESS INCOME',
|
'CONFERENCES, REGISTRATION', 'CONFERENCES, RELATED BUSINESS INCOME',
|
||||||
'BOOK ROYALTIES & AFFILIATE PROGRAMS', 'ADVERSITING',
|
'BOOK ROYALTIES & AFFILIATE PROGRAMS', 'ADVERSITING',
|
||||||
'TRADEMARKS', 'INTEREST INCOME', 'OTHER') {
|
'TRADEMARKS', 'INTEREST INCOME', 'OTHER') {
|
||||||
next if ($incomeGroups{$type}{output} =~ /^\s*$/ and $incomeGroups{$type}{total} == $ZERO);
|
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",
|
$incomeGroups{$type}{output}, "\n",
|
||||||
sprintf($formatStrTotal, "TOTAL $type:", Commify($incomeGroups{$type}{total}));
|
sprintf($formatStrTotal, "TOTAL $type:", Commify($incomeGroups{$type}{total}));
|
||||||
$overallTotal += $incomeGroups{$type}{total};
|
$overallTotal += $incomeGroups{$type}{total};
|
||||||
}
|
}
|
||||||
print INCOME "\n\n\n", sprintf($formatStrTotal, "OVERALL TOTAL:", Commify($overallTotal));
|
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}"
|
die "calculated total of $overallTotal does equal $incomeGroups{TOTAL}{total}"
|
||||||
if (abs($overallTotal) - abs($incomeGroups{TOTAL}{total}) > $ONE_PENNY);
|
if (abs($overallTotal) - abs($incomeGroups{TOTAL}{total}) > $ONE_PENNY);
|
||||||
|
|
@ -268,7 +302,7 @@ foreach my $type (keys %expenseGroups, 'TRAVEL') {
|
||||||
$expenseGroups{$type}{output} = "";
|
$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', '$',
|
my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$',
|
||||||
'-b', $startDate, '-e', $endDate,
|
'-b', $startDate, '-e', $endDate,
|
||||||
|
|
@ -291,6 +325,7 @@ foreach my $line (<FILE>) {
|
||||||
die "Weird account found, $account, with amount of $amount in expenses command\n"
|
die "Weird account found, $account, with amount of $amount in expenses command\n"
|
||||||
unless $account =~ /^\s*Expenses:/;
|
unless $account =~ /^\s*Expenses:/;
|
||||||
|
|
||||||
|
my $outputLine = "\"$account\",\"\$$amount\"\n";
|
||||||
my $taken = 0;
|
my $taken = 0;
|
||||||
# Note: Prioritize to put things under conference expenses if they were for a conference.
|
# Note: Prioritize to put things under conference expenses if they were for a conference.
|
||||||
foreach my $type ('CONFERENCES', keys %expenseGroups) {
|
foreach my $type ('CONFERENCES', keys %expenseGroups) {
|
||||||
|
|
@ -299,23 +334,23 @@ foreach my $line (<FILE>) {
|
||||||
next unless $line =~ /$expenseGroups{$type}{regex}/;
|
next unless $line =~ /$expenseGroups{$type}{regex}/;
|
||||||
$taken = 1;
|
$taken = 1;
|
||||||
$expenseGroups{$type}{total} += $amount;
|
$expenseGroups{$type}{total} += $amount;
|
||||||
$expenseGroups{$type}{output} .= " $line";
|
$expenseGroups{$type}{output} .= $outputLine;
|
||||||
}
|
}
|
||||||
if (not $taken) {
|
if (not $taken) {
|
||||||
if ($account =~ /Travel/) {
|
if ($account =~ /Travel/) {
|
||||||
$expenseGroups{'TRAVEL'}{total} += $amount;
|
$expenseGroups{'TRAVEL'}{total} += $amount;
|
||||||
$expenseGroups{'TRAVEL'}{output} .= " $line";
|
$expenseGroups{'TRAVEL'}{output} .= $outputLine;
|
||||||
} else {
|
} else {
|
||||||
$expenseGroups{'OTHER'}{total} += $amount;
|
$expenseGroups{'OTHER'}{total} += $amount;
|
||||||
$expenseGroups{'OTHER'}{output} .= " $line";
|
$expenseGroups{'OTHER'}{output} .= $outputLine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$firstTotal += $amount;
|
$firstTotal += $amount;
|
||||||
}
|
}
|
||||||
print EXPENSE " EXPENSES\n",
|
print EXPENSE "\"EXPENSES\",",
|
||||||
" Between $formattedStartDate and $formattedEndDate\n\n";
|
"\"STARTING:\",\"$formattedStartDate\",\"ENDING:\",\"$formattedEndDate\"\n\n";
|
||||||
$overallTotal = $ZERO;
|
$overallTotal = $ZERO;
|
||||||
$formatStrTotal = "%-90s \$%14s\n";
|
$formatStrTotal = "\"%-90s\",\"\$%14s\"\n";
|
||||||
|
|
||||||
my %verifyAllGroups;
|
my %verifyAllGroups;
|
||||||
foreach my $key (keys %expenseGroups) {
|
foreach my $key (keys %expenseGroups) {
|
||||||
|
|
@ -330,7 +365,7 @@ foreach my $type ('PAYROLL', 'SOFTWARE DEVELOPMENT', 'LICENSE ENFORCEMENT', 'CON
|
||||||
die "$type is not defined!" if not defined $expenseGroups{$type};
|
die "$type is not defined!" if not defined $expenseGroups{$type};
|
||||||
next if ($expenseGroups{$type}{output} =~ /^\s*$/ and $expenseGroups{$type}{total} == $ZERO);
|
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",
|
$expenseGroups{$type}{output}, "\n",
|
||||||
sprintf($formatStrTotal, "TOTAL $type:", Commify($expenseGroups{$type}{total}));
|
sprintf($formatStrTotal, "TOTAL $type:", Commify($expenseGroups{$type}{total}));
|
||||||
$overallTotal += $expenseGroups{$type}{total};
|
$overallTotal += $expenseGroups{$type}{total};
|
||||||
|
|
@ -338,7 +373,7 @@ foreach my $type ('PAYROLL', 'SOFTWARE DEVELOPMENT', 'LICENSE ENFORCEMENT', 'CON
|
||||||
|
|
||||||
print EXPENSE "\n\n\n", sprintf($formatStrTotal, "OVERALL TOTAL:", Commify($overallTotal));
|
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"
|
die "GROUPS NOT INCLUDED : ", join(keys(%verifyAllGroups), ", "), "\n"
|
||||||
unless (keys %verifyAllGroups == 0);
|
unless (keys %verifyAllGroups == 0);
|
||||||
|
|
@ -348,6 +383,80 @@ die "calculated total of $overallTotal does equal $firstTotal"
|
||||||
|
|
||||||
print STDERR "\n";
|
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";
|
||||||
|
|
||||||
|
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' ]);
|
||||||
|
|
||||||
|
my %trialBalanceData;
|
||||||
|
my %fullAccountList;
|
||||||
|
|
||||||
|
foreach my $id (keys %commands) {
|
||||||
|
my(@command) = @{$commands{$id}};
|
||||||
|
|
||||||
|
open(FILE, "-|", @command)
|
||||||
|
or die "unable to run command ledger command: @command: $!";
|
||||||
|
|
||||||
|
print STDERR ($VERBOSE ? "Running: @command\n" : ".");
|
||||||
|
|
||||||
|
foreach my $line (<FILE>) {
|
||||||
|
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 =~ /\<Adjustment\>/ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $curOn = 'Assets';
|
||||||
|
|
||||||
|
foreach my $account (sort preferredAccountSorting keys %fullAccountList) {
|
||||||
|
# Blank lines right
|
||||||
|
if ($account !~ /^$curOn/) {
|
||||||
|
print TRIAL "pagebreak\n";
|
||||||
|
$curOn = $account;
|
||||||
|
if ($curOn =~ /(Accrued:[^:]+):.*$/) {
|
||||||
|
$curOn = $1;
|
||||||
|
} else {
|
||||||
|
$curOn =~ s/^([^:]+):.*$/$1/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
#
|
#
|
||||||
# Local variables:
|
# Local variables:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
chart-of-accounts.csv
|
||||||
|
general-ledger.txt
|
||||||
|
general-ledger.csv
|
||||||
|
Financial/BankStuff/bank-statement.pdf
|
||||||
|
Financial/Invoices/Invoice20110510.pdf
|
||||||
|
Projects/Foo/Invoices/Invoice20100101.pdf
|
||||||
|
Projects/Foo/earmark-record.txt
|
||||||
|
Projects/Blah/Expenses/hosting/AprilHostingReceipt.pdf
|
||||||
|
Projects/Blah/Expenses/hosting/april-invoice.pdf
|
||||||
|
Projects/Foo/Expenses/hosting/AprilHostingReceipt.pdf
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
"CHART OF ACCOUNTS","BEGINNING:","2012/03/01","ENDING:","2012/02/29"
|
||||||
|
"Assets:Checking"
|
||||||
|
"Income:Donation"
|
||||||
|
"Income:Foo:Donation"
|
||||||
|
"Expenses:Blah:Hosting"
|
||||||
|
"Expenses:Foo:Hosting"
|
||||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
|
|
@ -1,4 +0,0 @@
|
||||||
Assets:Checking
|
|
||||||
Expenses:Foo:Hosting
|
|
||||||
Income:Donation
|
|
||||||
Income:Foo:Donation
|
|
||||||
Binary file not shown.
110
contrib/non-profit-audit-reports/unpaid-accruals-report.plx
Executable file
110
contrib/non-profit-audit-reports/unpaid-accruals-report.plx
Executable file
|
|
@ -0,0 +1,110 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
# unpaid-acccurals-report.plx -*- Perl -*-
|
||||||
|
|
||||||
|
# This report is designed to create what our accounts call a "Schedule of
|
||||||
|
# accounts payable". and "Schedule of accounts receivable".
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Copyright (C) 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
|
||||||
|
# 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;
|
||||||
|
use Date::Manip;
|
||||||
|
|
||||||
|
my $LEDGER_CMD = "/usr/local/bin/ledger";
|
||||||
|
|
||||||
|
my $ACCT_WIDTH = 70;
|
||||||
|
|
||||||
|
sub ParseNumber($) {
|
||||||
|
$_[0] =~ s/,//g;
|
||||||
|
return Math::BigFloat->new($_[0]);
|
||||||
|
}
|
||||||
|
Math::BigFloat->precision(-2);
|
||||||
|
my $ZERO = Math::BigFloat->new("0.00");
|
||||||
|
my $TWO_CENTS = Math::BigFloat->new("0.02");
|
||||||
|
|
||||||
|
if (@ARGV < 2) {
|
||||||
|
print STDERR "usage: $0 <START_DATE> <END_DATE> <LEDGER_OPTIONS>\n";
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
my($startDate, $endDate, @mainLedgerOptions) = @ARGV;
|
||||||
|
|
||||||
|
my $err;
|
||||||
|
my $formattedEndDate = UnixDate(DateCalc(ParseDate($endDate), ParseDateDelta("- 1 day"), \$err),
|
||||||
|
"%Y/%m/%d");
|
||||||
|
die "Date calculation error on $endDate" if ($err);
|
||||||
|
my $formattedStartDate = UnixDate(ParseDate($startDate), "%Y/%m/%d");
|
||||||
|
die "Date calculation error on $startDate" if ($err);
|
||||||
|
|
||||||
|
my(@ledgerOptions) = (@mainLedgerOptions,
|
||||||
|
'-V', '-X', '$', '-e', $endDate, '-F',
|
||||||
|
'\"%(tag("Invoice"))\",\"%A\",\"%(date)\",\"%(payee)\",\"%22.108t\"\n',
|
||||||
|
'--limit', 'tag("Invoice") !~ /^\s*$/', 'reg');
|
||||||
|
|
||||||
|
my @possibleTypes = ('Accrued:Loans Receivable', 'Accrued:Accounts Payable',
|
||||||
|
'Accrued:Accounts Receivable', 'Accrued:Expenses');
|
||||||
|
|
||||||
|
my %data;
|
||||||
|
foreach my $type (@possibleTypes) {
|
||||||
|
open(LEDGER_FUNDS, "-|", $LEDGER_CMD, @ledgerOptions, "/^$type/")
|
||||||
|
or die "Unable to run $LEDGER_CMD @ledgerOptions: $!";
|
||||||
|
|
||||||
|
while (my $line = <LEDGER_FUNDS>) {
|
||||||
|
next if $line =~ /"\<Adjustment\>"/;
|
||||||
|
die "Unable to parse output line $line from @ledgerOptions"
|
||||||
|
|
||||||
|
unless $line =~ /^\s*"([^"]+)","([^"]+)","([^"]+)","([^"]+)","\s*\$\s*([\-\d\.\,]+)"\s*$/;
|
||||||
|
my($invoice, $account, $date, $payee, $amount) = ($1, $2, $3, $4, $5);
|
||||||
|
$amount = ParseNumber($amount);
|
||||||
|
|
||||||
|
push(@{$data{$type}{$invoice}{entries}}, { account => $account, date => $date, payee => $payee, amount => $amount});
|
||||||
|
$data{$type}{$invoice}{total} = $ZERO unless defined $data{$type}{$invoice}{total};
|
||||||
|
$data{$type}{$invoice}{total} += $amount;
|
||||||
|
}
|
||||||
|
close LEDGER_FUNDS;
|
||||||
|
die "Failure on ledger command for $type: $!" unless ($? == 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
foreach my $type (keys %data) {
|
||||||
|
foreach my $invoice (keys %{$data{$type}}) {
|
||||||
|
delete $data{$type}{$invoice} if abs($data{$type}{$invoice}{total}) <= $TWO_CENTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach my $type (keys %data) {
|
||||||
|
delete $data{$type} if scalar(keys %{$data{$type}}) == 0;
|
||||||
|
}
|
||||||
|
foreach my $type (keys %data) {
|
||||||
|
print "\"SCHEDULE OF $type\"\n\"ENDING:\",\"$formattedEndDate\"\n\n",
|
||||||
|
'"DATE","PAYEE","ACCOUNT","AMOUNT","INVOICE"', "\n";
|
||||||
|
foreach my $invoice (keys %{$data{$type}}) {
|
||||||
|
my $vals;
|
||||||
|
foreach my $vals (@{$data{$type}{$invoice}{entries}}) {
|
||||||
|
print "\"$vals->{date}\",\"$vals->{payee}\",\"$vals->{account}\",\"\$$vals->{amount}\",\"link:$invoice\"\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print "pagebreak\n";
|
||||||
|
}
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Local variables:
|
||||||
|
# compile-command: "perl -c unpaid-accruals-report.plx"
|
||||||
|
# End:
|
||||||
|
|
||||||
|
|
@ -195,7 +195,10 @@ namespace {
|
||||||
|
|
||||||
for (const char * p = buf; *p; p++) {
|
for (const char * p = buf; *p; p++) {
|
||||||
if (*p == '.') {
|
if (*p == '.') {
|
||||||
if (commodity_t::decimal_comma_by_default ||
|
if (commodity_t::time_colon_by_default ||
|
||||||
|
(comm && comm->has_flags(COMMODITY_STYLE_TIME_COLON)))
|
||||||
|
out << ':';
|
||||||
|
else if (commodity_t::decimal_comma_by_default ||
|
||||||
(comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA)))
|
(comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA)))
|
||||||
out << ',';
|
out << ',';
|
||||||
else
|
else
|
||||||
|
|
@ -209,7 +212,10 @@ namespace {
|
||||||
out << *p;
|
out << *p;
|
||||||
|
|
||||||
if (integer_digits > 3 && --integer_digits % 3 == 0) {
|
if (integer_digits > 3 && --integer_digits % 3 == 0) {
|
||||||
if (commodity_t::decimal_comma_by_default ||
|
if (commodity_t::time_colon_by_default ||
|
||||||
|
(comm && comm->has_flags(COMMODITY_STYLE_TIME_COLON)))
|
||||||
|
out << ':';
|
||||||
|
else if (commodity_t::decimal_comma_by_default ||
|
||||||
(comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA)))
|
(comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA)))
|
||||||
out << '.';
|
out << '.';
|
||||||
else
|
else
|
||||||
|
|
@ -737,6 +743,16 @@ void amount_t::in_place_unreduce()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shifted) {
|
if (shifted) {
|
||||||
|
if ("h" == comm->symbol() && commodity_t::time_colon_by_default) {
|
||||||
|
amount_t floored = tmp.floored();
|
||||||
|
amount_t precision = tmp - floored;
|
||||||
|
if (precision < 0.0) {
|
||||||
|
precision += 1.0;
|
||||||
|
floored -= 1.0;
|
||||||
|
}
|
||||||
|
tmp = floored + (precision * (comm->smaller()->number() / 100.0));
|
||||||
|
}
|
||||||
|
|
||||||
*this = tmp;
|
*this = tmp;
|
||||||
commodity_ = comm;
|
commodity_ = comm;
|
||||||
}
|
}
|
||||||
|
|
@ -1090,6 +1106,9 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
|
||||||
bool decimal_comma_style
|
bool decimal_comma_style
|
||||||
= (commodity_t::decimal_comma_by_default ||
|
= (commodity_t::decimal_comma_by_default ||
|
||||||
commodity().has_flags(COMMODITY_STYLE_DECIMAL_COMMA));
|
commodity().has_flags(COMMODITY_STYLE_DECIMAL_COMMA));
|
||||||
|
bool time_colon_style
|
||||||
|
= (commodity_t::time_colon_by_default ||
|
||||||
|
commodity().has_flags(COMMODITY_STYLE_TIME_COLON));
|
||||||
|
|
||||||
new_quantity->prec = 0;
|
new_quantity->prec = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
bool commodity_t::decimal_comma_by_default = false;
|
bool commodity_t::decimal_comma_by_default = false;
|
||||||
|
bool commodity_t::time_colon_by_default = false;
|
||||||
|
|
||||||
void commodity_t::add_price(const datetime_t& date, const amount_t& price,
|
void commodity_t::add_price(const datetime_t& date, const amount_t& price,
|
||||||
const bool reflexive)
|
const bool reflexive)
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ protected:
|
||||||
#define COMMODITY_SAW_ANNOTATED 0x200
|
#define COMMODITY_SAW_ANNOTATED 0x200
|
||||||
#define COMMODITY_SAW_ANN_PRICE_FLOAT 0x400
|
#define COMMODITY_SAW_ANN_PRICE_FLOAT 0x400
|
||||||
#define COMMODITY_SAW_ANN_PRICE_FIXATED 0x800
|
#define COMMODITY_SAW_ANN_PRICE_FIXATED 0x800
|
||||||
|
#define COMMODITY_STYLE_TIME_COLON 0x1000
|
||||||
|
|
||||||
string symbol;
|
string symbol;
|
||||||
optional<std::size_t> graph_index;
|
optional<std::size_t> graph_index;
|
||||||
|
|
@ -176,6 +177,7 @@ protected:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static bool decimal_comma_by_default;
|
static bool decimal_comma_by_default;
|
||||||
|
static bool time_colon_by_default;
|
||||||
|
|
||||||
virtual ~commodity_t() {
|
virtual ~commodity_t() {
|
||||||
TRACE_DTOR(commodity_t);
|
TRACE_DTOR(commodity_t);
|
||||||
|
|
|
||||||
|
|
@ -352,9 +352,11 @@ option_t<session_t> * session_t::lookup_option(const char * p)
|
||||||
case 's':
|
case 's':
|
||||||
OPT(strict);
|
OPT(strict);
|
||||||
break;
|
break;
|
||||||
|
case 't':
|
||||||
|
OPT(time_colon);
|
||||||
|
break;
|
||||||
case 'v':
|
case 'v':
|
||||||
OPT(value_expr_);
|
OPT(value_expr_);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ public:
|
||||||
HANDLER(day_break).report(out);
|
HANDLER(day_break).report(out);
|
||||||
HANDLER(download).report(out);
|
HANDLER(download).report(out);
|
||||||
HANDLER(decimal_comma).report(out);
|
HANDLER(decimal_comma).report(out);
|
||||||
|
HANDLER(time_colon).report(out);
|
||||||
HANDLER(file_).report(out);
|
HANDLER(file_).report(out);
|
||||||
HANDLER(input_date_format_).report(out);
|
HANDLER(input_date_format_).report(out);
|
||||||
HANDLER(explicit).report(out);
|
HANDLER(explicit).report(out);
|
||||||
|
|
@ -130,6 +131,10 @@ public:
|
||||||
commodity_t::decimal_comma_by_default = true;
|
commodity_t::decimal_comma_by_default = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
OPTION_(session_t, time_colon, DO() {
|
||||||
|
commodity_t::time_colon_by_default = true;
|
||||||
|
});
|
||||||
|
|
||||||
OPTION__
|
OPTION__
|
||||||
(session_t, price_exp_, // -Z
|
(session_t, price_exp_, // -Z
|
||||||
CTOR(session_t, price_exp_) { value = "24"; });
|
CTOR(session_t, price_exp_) { value = "24"; });
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue