#!/usr/bin/perl -Tw
#
#  LMS version 1.8.12 Tagan
#
#  Copyright (C) 2001-2006 LMS Developers
#
#  Please, see the doc/AUTHORS for more information about authors!
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License Version 2 as
#  published by the Free Software Foundation.
#
#  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, write to the Free Software
#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
#  USA.
#
#  $Id: lms-payments,v 1.82.2.3 2006/01/16 09:35:17 alec Exp $

use strict;
use DBI;
use Config::IniFiles;
use Getopt::Long;
use vars qw($configfile $quiet $help $version $fakedate);
use POSIX qw(strftime);
use POSIX qw(mktime);
use Time::Local;

my $_version = '1.8.12 Tagan';

my %options = (
	"--config-file|C=s"	=>	\$configfile,
	"--quiet|q"		=>	\$quiet,
	"--help|h"		=>	\$help,
	"--version|v"		=>	\$version,
	"--fakedate|f=s"	=>	\$fakedate,
);

Getopt::Long::config("no_ignore_case");
GetOptions(%options);

if($help)
{
	print STDERR <<EOF;
lms-payments, version $_version
(C) 2001-2006 LMS Developers

-C, --config-file=/etc/lms/lms.ini	alternate config file (default: /etc/lms/lms.ini);
-h, --help			print this help and exit;
-v, --version			print version info and exit;
-q, --quiet			suppress any output, except errors;
-f, --fakedate=YYYY/MM/DD	override system date

EOF
	exit 0;
}

if($version)
{
	print STDERR <<EOF;
lms-payments, version $_version
(C) 2001-2006 LMS Developers

EOF
	exit 0;
}

if(!$configfile)
{
	$configfile = "/etc/lms/lms.ini";
}

if(!$quiet)
{
	print STDOUT "lms-payments, version $_version\n";
	print STDOUT "(C) 2001-2006 LMS Developers\n";
	print STDOUT "Using file $configfile as config.\n";
}

if(! -r $configfile)
{
	print STDERR "Fatal error: Unable to read configuration file $configfile, exiting.\n";
	exit 1;
}

my $ini = new Config::IniFiles -file => $configfile;
print @Config::IniFiles::errors;

my $dbtype = $ini->val('database', 'type') || 'mysql';
my $dbhost = $ini->val('database', 'host') || 'localhost';
my $dbuser = $ini->val('database', 'user') || 'root';
my $dbpasswd = $ini->val('database', 'password') || '';
my $dbname = $ini->val('database', 'database') || 'lms';

my $deadline = $ini->val('payments', 'deadline') || '14';
my $paytype = $ini->val('payments', 'paytype') || 'TRANSFER';
my $comment = $ini->val('payments', 'comment') || 'Tariff %tariff subscription for period %period';
my $suspension_description = $ini->val('payments', 'suspension_description') || '';

my $suspension_percentage = $ini->val('finances', 'suspension_percentage') || '0';

my $dbase;
my $utsfmt;

if($dbtype eq "mysql")
{
	$dbase = DBI->connect("DBI:mysql:database=$dbname;host=$dbhost","$dbuser","$dbpasswd", { RaiseError => 1 });
	$utsfmt = "UNIX_TIMESTAMP()";
}
elsif($dbtype eq "postgres")
{
	$dbase = DBI->connect("DBI:Pg:dbname=$dbname;host=$dbhost","$dbuser","$dbpasswd", { RaiseError => 1 });
	$utsfmt = "EXTRACT(EPOCH FROM CURRENT_TIMESTAMP(0))";
}
else
{
	print STDERR "Fatal error: unsupported database type: $dbtype, exiting.\n";
	exit 1;
}

sub localtime2()
{
	if($fakedate)
	{
		my @fakedate = split(/\//, $fakedate);
		return localtime(timelocal(0,0,0,$fakedate[2],$fakedate[1]-1,$fakedate[0]));
	}
	else
	{
		return localtime();
	}
}

use constant YEAR	=> 5;
use constant QUARTER	=> 4;
use constant MONTH	=> 3;
use constant WEEK	=> 2;
use constant DAY	=> 1;
use constant DISPOSABLE	=> 0;

my $currtime = strftime("%s",localtime2());
my $month = sprintf("%d",strftime("%m",localtime2()));
my $day = strftime("%e",localtime2());
my $dom = sprintf("%d",strftime("%d",localtime2()));
my $year = strftime("%Y",localtime2());
my $weekday = strftime("%u",localtime2());
my $yearday = strftime("%j",localtime2());

my $today;
if($fakedate) {
	$today = strftime("%s", localtime2());
} else {
	$today = timelocal(0,0,0,$dom,$month-1,$year);
}

my $quarter;
if($month==1 || $month==4 || $month==7 || $month==10) {
	$quarter = $day;
} elsif ($month==2 || $month==5 || $month==8 || $month==11) {
	$quarter = $day + 100;
} else {
	$quarter = $day + 200;
}

my %txts;
$txts{+DAY} = strftime("%Y/%m/%d", 0, 0, 12, $day, $month - 1, $year - 1900);
$txts{+WEEK} = strftime("%Y/%m/%d", 0, 0, 12, $day, $month - 1, $year - 1900)." - ".strftime("%Y/%m/%d", 0, 0, 12, $day + 7, $month - 1, $year - 1900);
$txts{+MONTH} = strftime("%Y/%m/%d", 0, 0, 12, $day, $month - 1, $year - 1900)." - ".strftime("%Y/%m/%d", 0, 0, 12, $day - 1, $month, $year - 1900);
$txts{+QUARTER} = strftime("%Y/%m/%d", 0, 0, 12, $day, $month - 1, $year - 1900)." - ".strftime("%Y/%m/%d", 0, 0, 12, $day - 1, $month + 2, $year - 1900);
$txts{+YEAR} = strftime("%Y/%m/%d", 0, 0, 12, $day, $month - 1, $year - 1900)." - ".strftime("%Y/%m/%d", 0, 0, 12, $day - 1, $month - 1, $year - 1900 + 1);

# special case, ie. you have 01.01.2005-01.31.2005 on invoice, but invoice/
# assigment is made not January, the 1st:

my $current_month = strftime("%Y/%m/%d", 0, 0, 12, 1, $month - 1, $year - 1900)." - ".strftime("%Y/%m/%d", 0, 0, 12, 0 , $month , $year - 1900);

my $dbq;
my $row;

if(! $suspension_percentage)
{
	$dbq = $dbase->prepare("SELECT value FROM uiconfig WHERE var = 'suspension_percentage' AND section = 'finances'");
	$dbq->execute();
	if($row = $dbq->fetchrow_hashref())
	{
		$suspension_percentage = $row->{'value'} || 0;
	}
}

my $start = 0;
my $end = 0;

# get ID and period of default invoices numbering plan
$dbq = $dbase->prepare("SELECT id, period FROM numberplans WHERE doctype = 1 AND isdefault = 1");
$dbq->execute();
$row = $dbq->fetchrow_hashref();
my $period = $row->{'period'} || YEAR;
my $numberplanid = $row->{'id'} || 0;

# calculate start and end of numbering period
for($period)
{
	/@{[DAY]}/ && do {
		$start = strftime("%s", 0, 0, 0, $day, $month - 1, $year - 1900);
		$end = strftime("%s", 0, 0, 0, $day + 1, $month - 1, $year - 1900);
		last;
	};
	/@{[WEEK]}/ && do {
		my $startweek = $dom - $weekday + 1;
		$start = strftime("%s", 0, 0, 0, $startweek, $month - 1, $year - 1900);
		$end = strftime("%s", 0, 0, 0, $startweek + 7, $month - 1, $year - 1900);
		last;
	};
	/@{[MONTH]}/ && do {
		$start = strftime("%s", 0, 0, 0, 1, $month - 1, $year - 1900);
		$end = strftime("%s", 0, 0, 0, 1, $month, $year - 1900);
		last;
	};
	/@{[QUARTER]}/ && do {
		my $startmonth;
		if($month<=3) {
			$startmonth = 1;
		} elsif ($month<=6) {
			$startmonth = 4;
		} elsif ($month<=9) {
			$startmonth = 7;
		} else {
			$startmonth = 10;
		}
		$start = strftime("%s", 0, 0, 0, 1, $startmonth - 1, $year - 1900);
		$end = strftime("%s", 0, 0, 0, 1, $startmonth - 1 + 3, $year - 1900);
		last;
	};
	# /@{[YEAR]}/ && do
	{
		$start = strftime("%s", 0, 0, 0, 1, 0, $year - 1900);
		$end = strftime("%s", 0, 0, 0, 1, 0, $year + 1 - 1900);
		last;
	};
}

$dbq = $dbase->prepare("SELECT MAX(number) AS number FROM documents WHERE cdate >= $start AND cdate < $end AND type = 1 AND numberplanid = $numberplanid");
$dbq->execute();
$row = $dbq->fetchrow_hashref();
my $number = $row->{'number'} || 0;
my %gotinv;
my $icdbq;
my $cdbq;
my $suspended = 0;
my $itemid;

# let's go, fetch *ALL* assignments in given day
$dbq = $dbase->prepare("SELECT tariffid, liabilityid, customerid, period, at, suspended, invoice, tariffs.description AS description,
	(CASE liabilityid WHEN 0 THEN tariffs.name ELSE liabilities.name END) AS name, 
	(CASE liabilityid WHEN 0 THEN tariffs.taxid ELSE liabilities.taxid END) AS taxid, 
	(CASE liabilityid WHEN 0 THEN tariffs.prodid ELSE liabilities.prodid END) AS prodid, 
	(CASE liabilityid WHEN 0 
		THEN (CASE suspended WHEN 0 
			THEN ROUND(CASE discount WHEN 0 THEN tariffs.value ELSE ((100-discount)*tariffs.value)/100 END, 2) 
			ELSE ROUND(ROUND(CASE discount WHEN 0 THEN tariffs.value ELSE ((100-discount)*tariffs.value)/100 END, 2) * $suspension_percentage/100, 2) 
			END)
		ELSE
		    (CASE suspended WHEN 0 
			THEN ROUND(CASE discount WHEN 0 THEN liabilities.value ELSE ((100-discount)*liabilities.value)/100 END, 2) 
			ELSE ROUND(ROUND(CASE discount WHEN 0 THEN liabilities.value ELSE ((100-discount)*liabilities.value)/100 END, 2) * $suspension_percentage/100, 2) 
			END)
		END) AS value
	FROM assignments
	LEFT JOIN tariffs ON (tariffid = tariffs.id)
	LEFT JOIN liabilities ON (liabilityid = liabilities.id)
	LEFT JOIN customers ON (customerid = customers.id)
	WHERE status = 3
		AND (period = ".DAY." 
		OR (period = ".WEEK." AND at = $weekday) 
		OR (period = ".MONTH." AND at = $day) 
		OR (period = ".QUARTER." AND at = $quarter) 
		OR (period = ".YEAR." AND at = $yearday) 
		OR (period = ".DISPOSABLE." AND at = $today)) 
		AND (datefrom <= $currtime OR datefrom = 0) AND (dateto > $currtime OR dateto = 0)
	ORDER BY customerid, invoice, value DESC");
$dbq->execute();

while(my $assign = $dbq->fetchrow_hashref())
{
	my $uid = $assign->{'customerid'};

	if ($assign->{'value'} == 0) { next; };

	# check, if all customer liabilities are suspended
	if ($suspended != $uid)
	{
		my $xdbq = $dbase->prepare("SELECT 1 FROM assignments, customers WHERE customerid = customers.id AND tariffid = 0 AND liabilityid = 0 AND (datefrom <= $currtime OR datefrom = 0) AND (dateto > $currtime OR dateto = 0) AND customerid = $uid");
		$xdbq->execute();
		if(my $xrow = $xdbq->fetchrow_hashref())
		{
			$suspended = $uid;
			if (!$assign->{'suspended'})
			{
				$assign->{'value'} = sprintf("%.2f", $assign->{'value'} * $suspension_percentage / 100);
			}
		}
	} else {
		if (!$assign->{'suspended'})
		{
			$assign->{'value'} = sprintf("%.2f", $assign->{'value'} * $suspension_percentage / 100);
		}
	}

	my $desc = $comment;
	if ($assign->{'liabilityid'})
	{
		$desc = $assign->{'name'}; # you can use variables in names of tariffless liabilities
	}
	$desc =~ s/\%tariff/$assign->{'name'}/g;
	$desc =~ s/\%desc/$assign->{'description'}/g;
	$desc =~ s/\%period/$txts{$assign->{'period'}}/g;
	$desc =~ s/\%current_month/$current_month/g;
	
	if ($suspension_percentage && ($assign->{'suspended'} || $suspended == $uid))
	{
		$desc .= " ".$suspension_description;
	}

	$gotinv{$uid} = 0 if not defined $gotinv{$uid};
	if($assign->{'value'} != 0)
	{
		if($assign->{'invoice'})
		{
			if($gotinv{$uid} eq 0)
			{
				$itemid = 0;
				$number++;
				my $udbq = $dbase->prepare("SELECT lastname, name, address, city, zip, ssn, ten FROM customers WHERE id=$uid");
				$udbq->execute();
				my $urow = $udbq->fetchrow_hashref();
				my $idbq = $dbase->prepare("INSERT INTO documents (number, numberplanid, type, customerid, name, address, zip, city, ten, ssn, cdate, paytime, paytype) VALUES (?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
				$idbq->execute($number, $numberplanid, $uid, $urow->{'lastname'}.' '.$urow->{'name'}, $urow->{'address'}, $urow->{'zip'}, $urow->{'city'}, $urow->{'ten'}, $urow->{'ssn'}, $currtime, $deadline, $paytype);
				$idbq = $dbase->prepare("SELECT id FROM documents WHERE number=$number AND cdate=$currtime AND type = 1 AND customerid=$uid");
				$idbq->execute();
				my $irow = $idbq->fetchrow_hashref();
				$gotinv{$uid} = $irow->{'id'};
			}
			
			my $icdbq = $dbase->prepare("SELECT itemid FROM invoicecontents WHERE tariffid=$assign->{'tariffid'} AND value=$assign->{'value'} AND docid=$gotinv{$uid} AND description=?");
			$icdbq->execute($desc);
			
			if(my $icrow = $icdbq->fetchrow_hashref())
			{
				$itemid = $icrow->{'itemid'};
				
				$icdbq = $dbase->prepare("UPDATE invoicecontents SET count=count+1 WHERE tariffid=$assign->{'tariffid'} AND docid=$gotinv{$uid} AND description=?");
				$icdbq->execute($desc);

				$cdbq = $dbase->prepare("UPDATE cash SET value = value+($assign->{'value'}*-1) WHERE docid = $gotinv{$uid} AND itemid = $itemid");
				$cdbq->execute();
			}
			else
			{
				$itemid++;

				$icdbq = $dbase->prepare("INSERT INTO invoicecontents (docid, value, taxid, prodid, content, count, description, tariffid, itemid) VALUES ($gotinv{$uid}, $assign->{'value'}, $assign->{'taxid'}, '$assign->{'prodid'}', 'szt', 1, ?, $assign->{'tariffid'}, $itemid)");
				$icdbq->execute($desc);
				
				$cdbq = $dbase->prepare("INSERT INTO cash (time, value, taxid, customerid, comment, docid, itemid) VALUES ($currtime, $assign->{'value'} * -1, $assign->{'taxid'}, $uid, ?, $gotinv{$uid}, $itemid)");
				$cdbq->execute($desc);
			}
		}
		else
		{
			$cdbq = $dbase->prepare("INSERT INTO cash (time, value, taxid, customerid, comment) VALUES ($currtime, $assign->{'value'} * -1, $assign->{'taxid'}, $uid, ?)");
			$cdbq->execute($desc);
		}

		print STDERR "CID:$uid\tVAL:$assign->{'value'}\tDESC:$desc\n" if not $quiet;

		if ($assign->{'liabilityid'} && $assign->{'period'} == DISPOSABLE) #remove liability
		{
			$dbase->do("DELETE FROM liabilities WHERE id = $assign->{'liabilityid'}");
		}
	}
}	

# solid payments
$dbq = $dbase->prepare("SELECT * FROM payments WHERE value <> 0 AND (period = ".DAY." OR (period = ".WEEK." AND at=$weekday) OR (period = ".MONTH." AND at=$day) OR (period = ".QUARTER." AND at = $quarter) OR (period = ".YEAR." AND at = $yearday))");
$dbq->execute();

while(my $assign = $dbq->fetchrow_hashref())
{
	my $sdbq = $dbase->prepare("INSERT INTO cash (time, type, value, customerid, comment) VALUES ($currtime, 1, $assign->{'value'} * -1, 0, ?)");
	$sdbq->execute($assign->{'name'}.'/'.$assign->{'creditor'});
	print STDERR "CID:0\tVAL:$assign->{'value'}\tDESC:$assign->{'name'}/$assign->{'creditor'}\n" if not $quiet;
}

$dbq = $dbase->prepare("SELECT liabilityid AS id FROM assignments WHERE dateto < $utsfmt - 86400 * 30 AND dateto != 0 AND liabilityid != 0");
$dbq->execute();
while(my $row = $dbq->fetchrow_hashref())
{
	$dbase->do("DELETE FROM liabilities WHERE id = $row->{'id'}");
}
$dbase->do("DELETE FROM assignments WHERE dateto < $utsfmt - 86400 * 30 AND dateto != 0");
$dbase->do("DELETE FROM assignments WHERE at = $today");
$dbase->do("DELETE FROM timestamps WHERE tablename = 'cash' OR tablename = '_global'");
$dbase->do("INSERT INTO timestamps (tablename,time) VALUES ('cash',$utsfmt)");
$dbase->do("INSERT INTO timestamps (tablename,time) VALUES ('_global',$utsfmt)");
$dbase->disconnect();
