#!/usr/bin/perl -w

=head1 NAME

dfxml-invoice - parse a QSF XML file and prepare a simple 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 SYNOPSIS

 dfxml-invoice FILENAME
 dfxml-invoice -h|--help|--version

=head1 DESCRIPTION

dfxml-invoice parses a QSF XML file output by pilot-qof
and prepares a simple invoice based on rates specified
in ~/.datafreedom/currency which will be created for you.

Specify '-' as the filename to parse STDIN.

e.g.
 pilot-qof -x data.xml --invoice-city -t 2006-11-09 | dfxml-invoice -

A more complex example using 'zenity' - a Gnome dialog generator.

 $ pilot-qof -x data.xml --invoice-city -t 2006-11-08 | dfxml-invoice - > /tmp/zenity
  zenity --text-info --title="2006-11-08" --filename=/tmp/zenity --width=500 --height=300

dfxml-invoice, like pilot-qof, is designed to be used in pipes like this. It is
intended to provide support for your own scripts where details like the date 
can be set as an option:

 #!/bin/bash
 DATE=$1
 
 if [ ! $DATE ]; then
	echo "$0: please specify the date of the invoice you want to view."
	exit
 fi
 pilot-qof -x data.xml --invoice-city -t $DATE | dfxml-invoice - | \
  zenity --text-info --title="$DATE" --width=500 --height=300

Note that if the currency uses a UTF-8 symbol, zenity may fail to 
display the symbol correctly unless the above code is adapted to
write to a temporary file and zenity then loads that file using the
'--filename' option. See Debian Bug# 410728. 
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=410728

Or, a full zenity version which allows the date to be selected in a calendar widget:

 #!/bin/bash
 set -e
 MSG="Select the date of the invoice to view"
 DATE=`zenity --calendar --text="$MSG" --date-format="%Y-%m-%d"`
 TIME=`date -d"$DATE" +"%A, %B %e %Y"`
 
 if [ ! $DATE ]; then
	echo "$0: please specify the date of the invoice you want to view."
	exit
 fi
 
 pilot-qof -x /opt/data/pilot-qof/offline.xml --invoice-city -t $DATE \
  | dfxml-invoice - | zenity --text-info --title="$TIME" --width=500 --height=400

=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 pilot-qof package, either
in the Debian BTS or via SourceForge trackers.

=head1 COPYRIGHT & LICENSE

  Copyright 2007 Neil Williams.

  This program 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/>.

=cut

# need to correlate Number::Format with the Palm Default Currency Table

require 5.004;
use strict;
use POSIX qw(locale_h);
use locale;
use Date::Format;
use XML::QOFQSF qw(QSFParse);
use Number::Format qw(:subs :vars);
use Config::Auto;
use File::HomeDir;
use Text::FormatTable;
use Math::BigInt;
use Data::Random qw(:all);

my $our_version = "0.0.1";
sub usageversion {
    print(STDERR <<END)
dfxml-invoice version $our_version

Usage:
 dfxml-invoice FILENAME
 dfxml-invoice -h|--help|--version

Options:
 -h|--help:           print this usage message and exit
 --version:           print this usage message and exit

dfxml-invoice parses a QSF XML file output by pilot-qof
and prepares a simple invoice based on rates specified
in ~/.datafreedom/currency which has been created for you.

Specify '-' as the filename to parse STDIN.

e.g.
pilot-qof -x data.xml --invoice-city -t 2006-11-09 | dfxml-invoice -

END
        || die "$0: failed to write usage: $!\n";
exit 0;
}

my $stdin = "false";
while( @ARGV ) {
    $_= shift( @ARGV );
    if (/^-$/) {
 		$stdin = "true";
    	next;
    }
    last if m/^--$/;
    if (!/^-/) {
        unshift(@ARGV,$_);
        last;
    }
    if (/^(-h|--help|--version)$/) {
        &usageversion();
        exit( 0 );
    }
}

my $home = File::HomeDir->my_home;
my $datafreedom_dir = "$home/.datafreedom";
mkdir ($datafreedom_dir) if (! -d $datafreedom_dir);
my $cfile = $datafreedom_dir . "/currency";
if (! -f $cfile)
{
	open (CFG, ">$cfile");
	print CFG "\# config file for datafreedom-perl\n";
	print CFG "\# used by xml-invoice\n";
	print CFG "\# Omit the currency symbols when specifying the rate\n";
	print CFG "\# and use . as the decimal point (i.e. locale C).\n";
	print CFG "\# e.g. for a rate of \$15.45\n";
	print CFG "\# weekday_hourly_rate: 15.45\n";
	print CFG "weekday_hourly_rate:\n";
	print CFG "weekend_hourly_rate:\n";
	print CFG "mileage_rate:\n";
	close (CFG);
	die "Cannot create config file $cfile: $!\n" if (! -f $cfile);
}

my $file = "";
$file = "-" if ($stdin eq "true");
$file = $ARGV[0] if ($stdin eq "false");
&usageversion if (!$file);
die "Cannot find $file. Please specify an invoice XML file.\n" 
	if ((! -f $file) && ($file ne '-'));

my $config = Config::Auto::parse("$cfile", format => "colon");
my $rate1 = 0;
$rate1 += $config->{'weekday_hourly_rate'}
	if ($config->{'weekday_hourly_rate'} ne "");
my $rate2 = 0;
$rate2 += $config->{'weekend_hourly_rate'} 
	if ($config->{'weekend_hourly_rate'} ne "");
my $mrate = 0;
$mrate += $config->{'mileage_rate'} 
	if ($config->{'mileage_rate'} ne "");

if (($rate1 == 0) || ($rate2 == 0) || ($mrate == 0))
{
	print "weekday_hourly_rate=$rate1\tweekend_hourly_Rate=$rate2\tmileage_rate=$mrate\n";
	die "Please specify some rates to use in $cfile\n" 
}

my $lconv = POSIX::localeconv();

my %obj = QSFParse("$file");
my $expenses = $obj{'pilot_expenses'};
my $contacts = $obj{'pilot_address'};
my $appointments =  $obj{'pilot_datebook'};
my $exp_count = @$expenses;
my $contact_count = @$contacts;
my $event_count = @$appointments;

if (($exp_count == 0) && ($contact_count == 0) && ($event_count == 0))
{
	warn "Empty file: $file\n";
	exit(0);
}

my $template = "%A, %o %B %Y";
my $template2 = "%H:%M:%S %P";
my $template3 = "%H hrs, %M mins";
my $currency = "";
$currency = $lconv->{int_curr_symbol} if ($lconv->{int_curr_symbol});
$currency =~ s/ //;
my $symbol = "";
$symbol = $lconv->{currency_symbol} if ($lconv->{currency_symbol});
$symbol =~ s/ //g;
my $hours = 0;
my $charge = 0;
my $miles = 0;
my $materials = 0;
my $decimal_point = ".";
$decimal_point = $lconv->{decimal_point} if ($lconv->{decimal_point});
my $thousands_sep = ",";
$thousands_sep = $lconv->{thousands_sep} if ($lconv->{thousands_sep});

my $amount = new Number::Format(-thousands_sep   => $thousands_sep,
                              -decimal_point   => $decimal_point,
                              -int_curr_symbol => $symbol);

my $table = Text::FormatTable->new('20l 40l');
$table->head('Charge', 'Item');
$table->rule('-');
my $cell = "";
my $cell2 = "";

# process the addressbook details of the contact to be invoiced.
foreach my $c (@$contacts)
{
	$cell = "\nBranch: " . $c->category . ". " . $c->entryCompany . ", ";
	$cell .= $c->entryAddress;
	$table->row('', $cell);
}
# calculate how long the work took and how much to charge
foreach my $a (@$appointments)
{
	my $diff = ($a->end_time - $a->start_time) / 3600;
	$cell = "";
	$cell2 = "";
	$cell .= time2str($template3, ($a->end_time - $a->start_time - 3600));
	$hours += ($a->end_time - $a->start_time) / 3600;
	if (time2str("%w", $a->start_time) ge "6")
	{
		$charge += $diff * $rate2;
		$cell .= " @ " . $amount->format_price($rate2, 2, 2) . "/hr";
		$cell2 .= $amount->format_price(($diff * $rate2), 2, 2);
		$table->row($cell2, $cell);
	}
	else
	{
		$charge += $diff * $rate1;
		$cell .= " @ " . $amount->format_price($rate1, 2, 2) . "/hr";
		$cell2 .= $amount->format_price(($diff * $rate1), 2, 2);
		$table->row($cell2, $cell);
	}
}
$table->rule('');
# Add expenses and mileage claims.
foreach my $e (@$expenses)
{
	$cell2 = "";
	$cell = "Expenses: ";
	if ($e->type_of_expense eq "Mileage")
	{
		$miles += $e->expense_amount;
		$cell .= $e->expense_city . " " . time2str($template, $e->expense_date) . "\n";
		$cell .= $e->expense_amount . " miles @ ";
		$cell .= $amount->format_price($mrate, 2) . "/mile = ";
		$cell2 .= $amount->format_price(($miles * $mrate), 2);
		$table->row($cell2, $cell);
	}
	else
	{
		$materials += $e->expense_amount;
		$cell .= $e->expense_city . " " . time2str($template, $e->expense_date) . "\n";
		warn ("Currency mismatch!" . $currency . ":" . $e->kvp_mnemonic) 
			if ($currency ne $e->kvp_mnemonic);
		$cell .= $e->type_of_expense . ": ";
		$cell2 .= $amount->format_price($e->expense_amount, 2);
		$table->row($cell2, $cell);
	}
}
my $gt = $amount->format_price(($charge + ($miles * $mrate) + $materials), 2);
$table->rule('-');
$table->row($gt, 'Grand Total');
$table->rule('-');
print $table->render(20);
