version 1.46, 2012/01/27 04:13:45 |
version 1.56, 2020/08/02 18:57:28 |
|
|
#!/usr/bin/perl |
#!/usr/bin/perl |
# $AFresh1: rt_invoices.pl,v 1.45 2011/12/31 02:14:32 andrew Exp $ |
# $AFresh1: rt_invoices.pl,v 1.55 2020/08/02 17:52:40 afresh1 Exp $ |
######################################################################## |
######################################################################## |
# Copyright (c) 2011 Andrew Fresh <andrew@afresh1.com> |
# Copyright (c) 2011 Andrew Fresh <andrew@afresh1.com> |
# |
# |
|
|
|
|
use 5.010; |
use 5.010; |
|
|
|
# Because we don't have a real cert |
|
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0; |
|
|
use Template; |
use Template; |
use RT::Client::REST; |
use RT::Client::REST; |
use RT::Client::REST::Ticket; |
use RT::Client::REST::Ticket; |
|
|
use File::Path; |
use File::Path; |
use DateTime; |
use DateTime; |
|
|
|
use List::Util qw/ sum /; |
|
|
use lib './lib'; # XXX This is fragile, there are better ways |
use lib './lib'; # XXX This is fragile, there are better ways |
use RTI::Config; |
use RTI::Config; |
use RTI::State; |
use RTI::State; |
|
|
|
|
if ( my $unpaid_invoices = $state->unpaid_invoices() ) { |
if ( my $unpaid_invoices = $state->unpaid_invoices() ) { |
foreach my $custid ( keys %{$unpaid_invoices} ) { |
foreach my $custid ( keys %{$unpaid_invoices} ) { |
my %project = ( title => 'Unpaid Invoices', fees => [], ); |
my %project |
|
= ( title => 'Unpaid Invoices', fees => [], no_total => 1 ); |
my $past_due = 0; |
my $past_due = 0; |
my $unpaid = 0; |
my $unpaid = 0; |
|
|
|
|
|
|
my $content |
my $content |
= sprintf( "Invoice %06d from %s", $id, $invdate->ymd ); |
= sprintf( "Invoice %06d from %s", $id, $invdate->ymd ); |
if ( $cust->{duedate} |
if ( $cust->{duedate} && $invdate < $cust->{duedate}) { |
&& DateTime->compare( $invdate, $cust->{duedate} ) > 0 ) |
|
{ |
|
$content = "PAST DUE: $content"; |
$content = "PAST DUE: $content"; |
$past_due += $unpaid_invoices->{$custid}->{$id}; |
$past_due += $unpaid_invoices->{$custid}->{$id}; |
} |
} |
|
|
} |
} |
|
|
if ($past_due) { |
if ($past_due) { |
$cust->{invoice} ||= make_invoice(); |
$cust->{invoice} ||= make_invoice($cust); |
|
|
$cust->{invoice}->{past_due} = $past_due; |
$cust->{invoice}->{past_due} = $past_due; |
$cust->{invoice}->{unpaid} = $unpaid; |
$cust->{invoice}->{unpaid} = $unpaid; |
$cust->{invoice}->{total_due} |
|
= $cust->{invoice}->{total} + $past_due + $unpaid; |
|
|
|
unshift @{ $cust->{invoice}->{projects} }, \%project; |
unshift @{ $cust->{invoice}->{projects} }, \%project; |
} |
} |
} |
} |
} |
} |
|
|
|
if ( my $credits = $state->credits ) { |
|
foreach my $custid ( keys %{$credits} ) { |
|
|
|
my $cust; |
|
foreach ( @{$customers} ) { |
|
if ( $_->{id} eq $custid ) { |
|
$cust = $_; |
|
last; |
|
} |
|
} |
|
|
|
next unless $cust; |
|
next unless $cust->{invoice}; |
|
next unless $credits->{$custid} < 0; |
|
|
|
$cust->{invoice}->{credit} = $credits->{$custid}; |
|
|
|
unshift @{ $cust->{invoice}->{projects} }, { |
|
title => 'Credits', |
|
no_total => 1, |
|
fees => [ |
|
{ contents => 'Available Credit', |
|
count => 1, |
|
rate => -$credits->{$custid}, |
|
} |
|
], |
|
}; |
|
} |
|
} |
|
|
foreach my $cust ( @{$customers} ) { |
foreach my $cust ( @{$customers} ) { |
my $invoice = $cust->{invoice}; |
my $invoice = $cust->{invoice} ||= make_invoice($cust); |
next unless $invoice && $invoice->{projects} && @{ $invoice->{projects} }; |
next unless $invoice && $invoice->{projects} && @{ $invoice->{projects} }; |
|
|
$invoice->{custid} = $cust->{id}; |
$invoice->{custid} = $cust->{id}; |
|
|
$subtotal += round( $expense->{amount} ); |
$subtotal += round( $expense->{amount} ); |
} |
} |
$project->{total} = $subtotal; |
$project->{total} = $subtotal; |
|
|
|
next if $project->{no_total}; |
$invoice->{total} += $subtotal; |
$invoice->{total} += $subtotal; |
} |
} |
@{ $invoice->{transactions} } = sort { $a <=> $b } keys %transactions; |
@{ $invoice->{transactions} } = sort { $a <=> $b } keys %transactions; |
|
|
$invoice->{total} -= round( $invoice->{discount}{amount} ); |
$invoice->{total} -= round( $invoice->{discount}{amount} ); |
} |
} |
|
|
|
if ($invoice->{past_due}) { |
|
$invoice->{total_due} |
|
= sum( @{ $invoice }{ qw/ total past_due unpaid / } ); |
|
} |
|
|
next unless $invoice->{total} > 0 || $invoice->{total_due}; |
next unless $invoice->{total} > 0 || $invoice->{total_due}; |
|
|
$invoice->{info} = $config->get('info'); |
$invoice->{info} = $config->get('info'); |
|
|
next unless $cust->{match}; |
next unless $cust->{match}; |
foreach my $m ( @{ $cust->{match} } ) { |
foreach my $m ( @{ $cust->{match} } ) { |
my $type = $m->{type}; |
my $type = $m->{type}; |
my $match |
my @things = map {lc} $ticket->$type; |
= exists $m->{$type} |
if ( exists $m->{$type} ) { |
? lc( $m->{$type} ) |
if ( !$m->{$type} ) { |
: qr/\Q$m->{regex}\E/; |
warn "Invalid match!"; |
my $thing = [ map {lc} $ticket->$type ]; |
next; |
|
} |
if ( !$match ) { |
my $match = lc $m->{$type}; |
warn "Invalid match!"; |
for my $thing (@things) { |
next; |
return $cust if $thing eq $match; |
|
} |
} |
} |
return $cust if ( $match ~~ $thing ); |
else { |
|
my $match = qr/\Q$m->{regex}\E/; |
|
for my $thing (@things) { |
|
return $cust if $thing =~ $match; |
|
} |
|
} |
} |
} |
} |
} |
|
|
|
|
sub make_invoice { |
sub make_invoice { |
my ($cust) = @_; |
my ($cust) = @_; |
|
|
my %invoice |
my %invoice = ( |
= ( end => $cust->{billend}->clone->subtract( seconds => 1 ) ); |
end => $cust->{billend}->clone->subtract( seconds => 1 ), |
|
total => 0, |
|
); |
$invoice{start} = $cust->{startinvoicedate}->clone |
$invoice{start} = $cust->{startinvoicedate}->clone |
if $cust->{startinvoicedate}; |
if $cust->{startinvoicedate}; |
|
|
|
|
while ( $date < $billend ) { |
while ( $date < $billend ) { |
my $start = $date->clone; |
my $start = $date->clone; |
|
|
$date->add($freq); |
$date->add_duration($freq); |
|
|
my $end = $date > $billend ? $billend->clone : $date->clone; |
my $end = $date > $billend ? $billend->clone : $date->clone; |
$end->subtract( seconds => 1 ); |
$end->subtract( seconds => 1 ); |
|
|
# XXX Only need $ticket for the alternate subject |
# XXX Only need $ticket for the alternate subject |
|
|
my $work_time = sprintf "%.03f", $txn->time_taken / 60; |
my $work_time = sprintf "%.03f", $txn->time_taken / 60; |
my $work_type = $txn->cf('WorkType'); |
my $work_type = $txn->cf('WorkType') || ''; |
|
|
if ( $work_type =~ s/\s*Onsite//i ) { |
if ( $work_type =~ s/\s*Onsite//i ) { |
|
|
|
|
my $per = $cust->{per} || 'week'; |
my $per = $cust->{per} || 'week'; |
my $freq = $cust->{frequency} || 1; |
my $freq = $cust->{frequency} || 1; |
|
|
my $day_method; |
my $day_method |
given ($per) { |
= $per eq 'week' ? 'dow' |
when ('week') { $per = 'weeks'; $day_method = 'dow' } |
: $per eq 'month' ? 'day' |
when ('month') { $per = 'months'; $day_method = 'day' } |
: die "Unknown per [$per]\n"; |
default { die "Unknown per [$per]\n" } |
|
} |
|
|
|
return DateTime::Duration->new( $per => $freq ), $day_method; |
return DateTime::Duration->new( "${per}s" => $freq ), $day_method; |
} |
} |
|
|
sub set_dates { |
sub set_dates { |
|
|
} |
} |
} |
} |
|
|
return $newest_invoice->clone->subtract($max_duration) |
$newest_invoice ||= DateTime->now; |
|
|
|
return $newest_invoice->clone->subtract_duration($max_duration) |
->subtract( days => 1 ); |
->subtract( days => 1 ); |
} |
} |
|
|
|
|
my $end = DateTime->now( time_zone => 'local' ) |
my $end = DateTime->now( time_zone => 'local' ) |
->set( hour => 0, minute => 0, second => 0 ); |
->set( hour => 0, minute => 0, second => 0 ); |
|
|
my $start = $end->clone->subtract($freq); |
my $start = $end->clone->subtract_duration($freq); |
|
|
# XXX This is helpful, but monthly and billday > 28 == !!! |
# XXX This is helpful, but monthly and billday > 28 == !!! |
$end->subtract( days => 1 ) while $day && $end->$day_method != $day; |
$end->subtract( days => 1 ) while $day && $end->$day_method != $day; |
|
|
? DateTime->now->subtract( days => $cust->{net} ) |
? DateTime->now->subtract( days => $cust->{net} ) |
: 0; |
: 0; |
|
|
$cust->{no_invoice} = 1 if $start->clone->add($freq) > $end; |
$cust->{no_invoice} = 1 if $start->clone->add_duration($freq) > $end; |
$cust->{billend} = $end; |
$cust->{billend} = $end; |
$cust->{billstart} = $start; |
$cust->{billstart} = $start; |
} |
} |