#!/usr/bin/perl

#  This package is free software; you can redistribute it and/or modify
#  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.  If not, see <http://www.gnu.org/licenses/>.

use POSIX qw(locale_h);
use strict;
use warnings;
use XML::QOFQSF qw(QSFParse QSFWrite);
use Date::Parse;
use Date::Format;
use Data::Dumper;
use Config::Auto;
use File::HomeDir;
use Number::Format;
use Text::FormatTable;
use Locale::gettext;
use vars qw / $amount $year $date $month $expenses $basefile %paid
 $category $mileage %rates %branches $time $template $filter @dates /;

# depends on pilot-qof

setlocale(LC_MESSAGES, "");
textdomain("datafreedom");
my $home = File::HomeDir->my_home;
# safeguard, just in case.
$home = "/tmp" if (!$home);
my $path =  "$home/.datafreedom";
mkdir $path if (! -d $path);
$mileage = 0;
my $cfile = $path . "/invoice.conf";
if (! -f $cfile)
{
	open (CFG, ">$cfile");
	print CFG _g("\# config file for datafreedom-perl invoices\n");
	print CFG _g("\# used by dfinvoice*\n");
	print CFG _g("\# use a [General] section:\n");
	print CFG "\# [General]\n";
	print CFG "\# category=mybusiness\n";
	print CFG "\# basefile=/home/user/pilot/data.xml\n";
	print CFG _g("\# Use one section per branch/location.\n");
	print CFG "\# [Acacia Avenue]\n";
	print CFG "\# code=1234\n";
	print CFG "\# rate=24\n";
	print CFG _g("\# Omit the currency symbols when specifying the rate\n");
	print CFG _g("\# and use . as the decimal point (i.e. locale C).\n");
	print CFG _g("\# e.g. for a rate of \$15.45\n");
	print CFG _g("\# rate=15.45\n");
	print CFG "[General]\n";
	print CFG "category=\n";
	print CFG "basefile=\n";
	print CFG "mileage=0\n";
	print CFG "default_rate=0\n";
	print CFG "default_code=0\n";
	close (CFG);
	die _g("Cannot create config file %s"), $cfile, ": $!\n" if (! -f $cfile);
}

my $config = Config::Auto::parse("$cfile", format => "ini");
$mileage = $config->{'General'}->{'mileage'}
	if (exists $config->{'General'}->{'mileage'});
$category = $config->{'General'}->{'category'}
	if (exists $config->{'General'}->{'category'});
$basefile = $config->{'General'}->{'basefile'}
	if (exists $config->{'General'}->{'basefile'});
if ((not defined $basefile) or (not -f $basefile))
{
	die _g("No basefile defined in config file: %s"), $cfile."\n";
}
%paid=();
my $pfile = $path . "/paid";
if (-f $pfile)
{
	my $payments = Config::Auto::parse ("$pfile", format => "colon");
	foreach my $rec (sort keys %$payments)
	{
		my $a = join(" ", @{$payments->{$rec}});
		$paid{$rec}=$a;
	}
	printf _g("Found payment references file: %d"),
		scalar keys %paid;
	print " ";
	printf ngettext ("listing", "listings", scalar keys %paid);
	print ".\n";
}
%rates = ();
%branches = ();
foreach my $f (sort keys %$config)
{
	next if ($f eq 'General');
	$rates{$f} = (defined $config->{$f}->{'rate'}) ? 
		$config->{$f}->{'rate'} : $config->{'General'}->{'default_rate'};
	$branches{$f} = (defined $config->{$f}->{'code'}) ?
		$config->{$f}->{'code'} : $config->{'General'}->{'default_code'};
}

$time = time;
# use last month to get a complete month.
my $advance = 1;

while( @ARGV ) {
	$_= shift( @ARGV );
	last if m/^--$/;
	if (!/^-/) {
		unshift(@ARGV,$_);
		last;
	}
	elsif (/^(-\?|-h|--help|--version)$/) {
		print _g("-m|--month - numbers of months to backtrack.");
		exit( 0 );
	}
	elsif (/^(-m|--month)$/) {
		$advance = shift;
	}
}
# ensure advance is negative.
$advance *= -1 if ($advance > 0);
my $advmonth = `LC_ALL=C date -d${advance}month +"%m"`;
chomp ($advmonth);
my $advyear = `LC_ALL=C date -d${advance}month +"%Y"`;
chomp ($advyear);
$month = $advmonth;
$year = $advyear;
$template = "%Y-%m";
$filter = "${year}-${month}";
my $tmp = `mktemp /tmp/invoice.XXXXXX`;
chomp ($tmp);
my $catstr = ($category ne '') ? "-c $category" : "";
system ("pilot-qof $catstr -i $basefile -t $filter -d pilot_datebook -w $tmp");
my %obj = QSFParse("$tmp");
my $datebook = $obj{'pilot_datebook'};
%obj = ();
my %hdates=();
foreach my $d (@$datebook)
{
	$template = "%Y-%m-%d";
	my $date = time2str($template, $d->start_time);
	$hdates{$date} = $d->start_time;
}

@dates = sort keys %hdates;
printf ngettext ("Found %d working day in %s.",
	"Found %d working days in %s.", scalar keys %hdates),
	scalar keys %hdates, $filter;
print "\n";

if (scalar keys %paid == scalar keys %hdates)
{
	# Translators: numbers are year and then month
	printf _g("%d-%02d appears to have been paid in full."),
		$year, $month;
	print "\n";
}
elsif (scalar keys %paid < scalar keys %hdates)
{
	# Translators: numbers are year and then month
	printf _g("%d-%02d appears not to have been paid in full."),
		$year, $month . "\n";
}
unlink ($tmp);
my %payment = ();
my %invoiced = ();
my $template = "%A, %o %B %Y";
my $x = new Number::Format;
my $grand = 0;
my $table = Text::FormatTable->new('|20l|5l|12l|30l|10r|25r|');
print "\n";
$table->rule('-');
$table->head(_g("Branch Name"), "#", _g("Date"), _g("Details"),
	_g("Amount"), _g("Payment Reference"));
$table->rule('=');
foreach $filter (@dates)
{
	my $total_amount = 0;
	%obj = ();
	my @db=();
	undef $datebook;
	undef $expenses;
	foreach my $k (keys %obj)
	{
		delete $obj{$k};
	}
	$tmp = `mktemp /tmp/invoice.XXXXXX`;
	chomp ($tmp);
	printf _g("Processing data for %s . . . \n"), $filter;
	system ("pilot-qof -i $basefile -c $category -t $filter --invoice-city -w $tmp");
	%obj = QSFParse("$tmp");
	unlink ($tmp);
	my $expenses = $obj{'pilot_expenses'};
	$datebook = $obj{'pilot_datebook'};
	my $address  = $obj{'pilot_address'};
	%obj = ();
	my %db = ();
	my %guid=();
	foreach my $d (@$datebook)
	{
		$template = "%Y-%m-%d";
		$date = time2str($template, $d->start_time);
		next if ($date ne $filter);
		my $hours = ($d->end_time - $d->start_time)/(60*60);
		next if (exists $guid{$d->guid});
		$guid{$d->guid}++;
		my $thisrate = (exists $rates{$d->description}) ? 
			$rates{$d->description} : $config->{'General'}->{'default_rate'};
		# bug - description needs 0.05
		$total_amount += $thisrate * $hours;
		$amount = $x->format_price(($thisrate * $hours), 2, 'CURRENCY_SYMBOL');
		$thisrate = $x->format_price($thisrate, 2, 'CURRENCY_SYMBOL');
		chomp ($amount);
		my $code = (not exists $branches{$d->description}) ?
			"" : $branches{$d->description};
		my $msg = sprintf (_g("%d hours at %s per hour"), $hours, $thisrate);
		my $ref = (exists $paid{$date}) ? $paid{$date} : "";
		$table->row($d->description, $code,
			$date, $msg, $amount, $ref);
		$template = "%d.%m.%y";
		$date = time2str($template, $d->start_time);
	}
	foreach my $a (@$expenses)
	{
		$template = "%Y-%m-%d";
		$date = time2str($template, $a->expense_date);
		next if ($date ne $filter);
		if ($a->type_of_expense eq "Mileage")
		{
			my $cost = $a->expense_amount * $mileage;
			$amount = $x->format_price($cost, 2, 'CURRENCY_SYMBOL');
			my $code = (not exists $branches{$a->expense_city}) ?
				"" : $branches{$a->expense_city};
			# bug: distance_unit => category prior to 0.05
			$table->row($a->expense_city, $code,
				time2str($template, $a->expense_date),
				$a->expense_amount . " " . lc($a->distance_unit),
				$amount, "");
			$total_amount += ($a->expense_amount * $mileage);
		}
		else
		{
			# bug: KVP symbol not going through UTF-8 well.
			# the table format also has issues with UTF-8 symbols.
			$amount = $x->format_price($a->expense_amount, 2, 'CURRENCY_SYMBOL');
			my $code = (not exists $branches{$a->expense_city}) ?
				"" : $branches{$a->expense_city};
			$table->row($a->expense_city, $code,
				time2str($template, $a->expense_date),
				lc($a->type_of_expense), $amount, "");
			$total_amount += $a->expense_amount;
		}
	}
	$amount = $x->format_price($total_amount, 2, 'CURRENCY_SYMBOL');
	$table->rule('-');
	$table->row("", "", "", _g("Daily total"), "", $amount);
	$table->rule('-');
	$grand += $total_amount;
}
print "\n".$table->render(20);
$amount = $x->format_price($grand, 2, 'CURRENCY_SYMBOL');
printf _g("Grand Total for %d-%02d: %s"), $year, $month, $amount."\n\n";

sub _g {
	return gettext(shift);
}

=head1 NAME

dfinvoice-month - parse a QSF XML file show pending payments

=head1 SYNOPSIS

 dfinvoice-month [-m|--month INTEGER]

 -m|--month is the number of months to backtrack when looking
 for data - the default is 1 to get a complete month of content.

=head1 Summary

Using a configuration file in the user home directory,
F</home/user/.datafreedom/invoice.conf>, the script reads the
Palm data synchronised using C<pilot-qof --invoice-city>, combines the
data with location and rate details in the configuration file and
outputs a table summarising the invoices and costs.

Example configuration:

 [General]
 mileage=0.30
 default_rate=24
 default_code=0
 basefile=/home/user/pilot-qof/offline.xml
 category=mybusiness

 [Acacia Avenue]
 rate=25
 code=1234

This configuration sets a mileage rate (expenses charged for personal
transport for business purposes, per unit distance) and a default
hourly rate for the work itself. Other expenses are read in from the
C<pilot-qof> data in the F<basefile>. If C<category> is set, the string
is passed to the C<--category> option of C<pilot-qof> to isolate
specific categories of appointments, expenses and contacts from the rest
of your data. (Only one category is supported.)

For places where the rates differ, location-specific sections can be
added - the hourly rate overrides the default rate and the code is
meant to be a shorthand for the location itself. e.g. if Head Office
etc. puts a code in the invoice payment notice to represent the location
or the cost code and similar, to help you identify the appropriate line
of content in the notice itself.

=head1 Payments

Payments can also be handled with a simple list in F<~/.datafreedom/paid>
where each line refers to one invoice (even if more than one invoice
is paid with the same reference). The list needs a simple format:

 date : reference

where 'date' matches the date format used by the C<pilot-qof -t> option:
e.g. 2009-11-24 for the 24th day of the 11th month in the year 2009.

 $ date +%Y-%m-%d

Whatever text occurs after the colon until the end of the line
is used as the payment reference.

Only one reference can be supported for any one date.

If no invoice exists for the date for any given reference, that reference
is ignored in the output but is still counted.

=head1 Assumptions

The design of the script and the structure of the data suits particular
types of businesses (specifically mine) and might not suit others so well.

The main objective is to support "consultant" or "service" industries
rather than "retail" or "commodity" industries - with a further assumption
that work is performed and paid on the basis of single dates with not
more than one invoice per day, although one payment can cover more than
one date and therefore more than one invoice.

=head1 DATAFREEDOM

These scripts developed from the 'pilot-qof' package but now include
support for other packages and formats and will continue to be extended
along the lines of http://www.data-freedom.org/ - liberating user data 
from the application. Therefore, the datafreedom scripts use a 'df' prefix.

The scripts continue to be developed within the pilot-qof CVS until such
time as the scripts are sufficiently cohesive to form a new source package.

Please feel free to contribute any of your own scripts, under the provisions
of the GNU General Public Licence v3 or later, via the QOF-devel mailing list.
http://lists.sourceforge.net/lists/listinfo/qof-devel

=head1 VERSION

Version 0.0.1

=head1 OBJECTS

pilot_expenses is part of pilot-qof.
Can also be used with gpe-expenses - compatibility with the
default SQLite gpe-expenses backend is pending.

L<http://qof.sourceforge.net/>

L<http://pilot-qof.sourceforge.net/>

L<http://gpe-expenses.sourceforge.net/>

=head1 AUTHOR

Neil Williams, C<< <codehelp at debian.org> >>

=head1 BUGS

Please report bugs via the datafreedom-perl package
in the Debian BTS or via the pilot-qof project and the
SourceForge trackers.
