#!/usr/bin/perl

=head1 NAME

dfxml-homebank - parse a QSF XML file and prepare a CSV invoice for homebank

=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-homebank FILENAME
 dfxml-homebank -h|--help|--version

=head1 DESCRIPTION

dfxml-homebank parses a QSF XML file output by pilot-qof
and prepares a CSV invoice suitable for import into homebank,
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-homebank -

The CSV output is intended to be compatible with homebank:
 DATE;TRANS_TYPE;EXP_TYPE;CUSTOMER;DESC;AMOUNT;ACCOUNT

e.g.
 13/08/2007;5;;ACustomer;Payment;-610.5;Income
 30/08/2007;4;Parking;BCustomer;JobTitle;2.75;Expenses

Homebank uses digits for transaction types:
 5 = internal transfer
 4 = bank transfer

Homebank CSV import can take a few steps (in common with a lot of other
CSV processes, this is due to inherent weaknesses in the CSV format)
so dfxml-homebank is best used with the '>>' redirect to append to an
existing CSV file so that all relevant invoices can be imported into
Homebank in a single import - each run of dfxml-homebank only adds a
few CSV lines, homebank is suprisingly efficient at CSV import.

dfxml-homebank, 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-homebank -

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-homebank -

=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 2008 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 warnings;
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::CSV_XS;
use Math::BigInt;
use Data::Random qw(:all);
use vars qw/ $date $trans_type $exp_type $customer
 $description $amount $account $csv %columns /;

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

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

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

dfxml-homebank parses a QSF XML file output by pilot-qof
and prepares a CSV invoice for homebank 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-homebank -

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 dfxml-invoice and dfxml-homebank\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";
	print CFG "workdir:\n";
	close (CFG);
}
if (! -f $cfile)
{
	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 = "%d/%m/%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});
 
$csv = Text::CSV_XS->new ();
my $amount = new Number::Format(-thousands_sep   => $thousands_sep,
								-decimal_point   => $decimal_point,
								-int_curr_symbol => '');

# process the addressbook details of the contact to be invoiced.
foreach my $c (@$contacts)
{
	$columns{'customer'} = $c->category;
	$columns{'description'} = $c->entryCompany;
}
# calculate how long the work took and how much to charge
foreach my $a (@$appointments)
{
	my $diff = ($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;
		$columns{'amount'} = $amount->format_number($rate2, 2, 1);
		$columns{'total'} = $amount->format_number(($diff * $rate2), 2, 1);
	}
	else
	{
		$charge += $diff * $rate1;
		$columns{'amount'} = $amount->format_number($rate1, 2, 1);
		$columns{'total'} = $amount->format_number(($diff * $rate1), 2, 1);
	}
}

# Add expenses and mileage claims.
foreach my $e (@$expenses)
{
	if ($e->type_of_expense eq "Mileage")
	{
		$miles += $e->expense_amount;
		$columns{'city'} = $e->expense_city;
		$columns{'date'} = time2str($template, $e->expense_date);
		$columns{'exp_amount'} = $e->expense_amount;
		$columns{'exp_price'} = $amount->format_number($mrate, 2, 1);
		$columns{'exp_total'} = $amount->format_number(($miles * $mrate), 2, 1);
	}
	else
	{
		$materials += $e->expense_amount;
		$columns{'city'} = $e->expense_city;
		$columns{'date'} = time2str($template, $e->expense_date);
		$columns{'exp_type'} = $e->type_of_expense;
		$columns{'exp_amount'} = $amount->format_number($e->expense_amount, 2, 1);
	}
}
my @vals = ();

push @vals, $columns{'date'};
push @vals, "4";
push @vals, $columns{'exp_type'};
push @vals, $columns{'customer'};
push @vals, $columns{'description'};
push @vals, $columns{'amount'};
push @vals, "Hours";
$csv->combine (@vals);
use Data::Dumper;
print $csv->string() . "\n";
