From 8cc48c6059d05fb54bf714aaddc168c8043e24ed Mon Sep 17 00:00:00 2001 From: Jonathan Prykop Date: Mon, 18 Jul 2016 19:29:59 -0500 Subject: [PATCH] RT#38973: Bill for time worked on ticket resolution [fully functional] --- FS/FS/TicketSystem/RT_Internal.pm | 37 ++++++++++++++++-- FS/FS/cust_pkg.pm | 14 +++++++ FS/FS/part_pkg/rt_field.pm | 66 +++++++++++++++++++++++--------- httemplate/edit/part_pkg.cgi | 4 +- httemplate/elements/select-rt-queue.html | 24 ++++++++++++ 5 files changed, 122 insertions(+), 23 deletions(-) create mode 100644 httemplate/elements/select-rt-queue.html diff --git a/FS/FS/TicketSystem/RT_Internal.pm b/FS/FS/TicketSystem/RT_Internal.pm index 01806c1b9..99e7044fa 100644 --- a/FS/FS/TicketSystem/RT_Internal.pm +++ b/FS/FS/TicketSystem/RT_Internal.pm @@ -3,6 +3,7 @@ package FS::TicketSystem::RT_Internal; use strict; use vars qw( @ISA $DEBUG $me ); use Data::Dumper; +use Date::Format qw( time2str ); use MIME::Entity; use FS::UID qw(dbh); use FS::CGI qw(popurl); @@ -101,17 +102,43 @@ sub init { warn "$me init: complete" if $DEBUG; } -=item customer_tickets CUSTNUM [ LIMIT ] [ PRIORITYVALUE ] +=item customer_tickets CUSTNUM [ PARAMS ] Replacement for the one in RT_External so that we can access custom fields -properly. +properly. Accepts a hashref with the following parameters: + +number - custnum/svcnum + +limit + +priority + +status + +queueid + +resolved - only return tickets resolved after this timestamp =cut # create an RT::Tickets object for a specified custnum or svcnum sub _tickets_search { - my( $self, $type, $number, $limit, $priority, $status, $queueid ) = @_; + my $self = shift; + my $type = shift; + + my( $number, $limit, $priority, $status, $queueid, $opt ); + if ( ref($_[0]) eq 'HASH' ) { + $opt = shift; + $number = $$opt{'number'}; + $limit = $$opt{'limit'}; + $priority = $$opt{'priority'}; + $status = $$opt{'status'}; + $queueid = $$opt{'queueid'}; + } else { + ( $number, $limit, $priority, $status, $queueid ) = @_; + $opt = {}; + } $type =~ /^Customer|Service$/ or die "invalid type: $type"; $number =~ /^\d+$/ or die "invalid custnum/svcnum: $number"; @@ -161,6 +188,10 @@ sub _tickets_search { $rtql .= " AND Queue = $queueid " if $queueid; + if ($$opt{'resolved'}) { + $rtql .= " AND Resolved >= " . dbh->quote(time2str('%Y-%m-%d %H:%M:%S',$$opt{'resolved'})); + } + warn "$me _customer_tickets_search:\n$rtql\n" if $DEBUG; $Tickets->FromSQL($rtql); diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 8d16fe042..bbb281ade 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -533,6 +533,7 @@ sub delete { # cust_bill_pay.pkgnum (wtf, shouldn't reference pkgnum) # cust_pkg_usage.pkgnum # cust_pkg.uncancel_pkgnum, change_pkgnum, main_pkgnum, and change_to_pkgnum + # rt_field_charge.pkgnum # cust_svc is handled by canceling the package before deleting it # cust_pkg_option is handled via option_Common @@ -2624,6 +2625,19 @@ sub change { return "canceling old package: $error"; } + # transfer rt_field_charge, if we're not changing pkgpart + # after billing of old package, before billing of new package + if ( $same_pkgpart ) { + foreach my $rt_field_charge ($self->rt_field_charge) { + $rt_field_charge->set('pkgnum', $cust_pkg->pkgnum); + $error = $rt_field_charge->replace; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "transferring rt_field_charge: $error"; + } + } + } + if ( $conf->exists('cust_pkg-change_pkgpart-bill_now') ) { #$self->cust_main my $error = $cust_pkg->cust_main->bill( diff --git a/FS/FS/part_pkg/rt_field.pm b/FS/FS/part_pkg/rt_field.pm index 1b18720ac..657a8d72c 100644 --- a/FS/FS/part_pkg/rt_field.pm +++ b/FS/FS/part_pkg/rt_field.pm @@ -24,12 +24,21 @@ my %custom_field = ( 'lookuptype' => 'RT::Queue-RT::Ticket', ); +my %multiple = ( + 'multiple' => 1, + 'parse' => sub { @_ }, # because /edit/process/part_pkg.pm doesn't grok select multiple +); + our %info = ( 'name' => 'Bill from custom fields in resolved RT tickets', 'shortname' => 'RT custom rate', 'weight' => 65, 'inherit_fields' => [ 'global_Mixin' ], 'fields' => { + 'queueids' => { 'name' => 'Queues', + 'type' => 'select-rt-queue', + %multiple, + }, 'unit_field' => { 'name' => 'Units field', %custom_field, 'validate' => sub { return ${$_[1]} ? '' : 'Units field must be specified' }, @@ -42,8 +51,7 @@ our %info = ( 'validate' => \&FS::part_pkg::global_Mixin::validate_moneyn }, 'display_fields' => { 'name' => 'Display fields', %custom_field, - 'multiple' => 1, - 'parse' => sub { @_ }, # because /edit/process/part_pkg.pm doesn't grok select multiple + %multiple, }, # from global_Mixin, but don't get used by this at all 'unused_credit_cancel' => {'disabled' => 1}, @@ -58,22 +66,24 @@ our %info = ( if $options->{'rate_field'} and $options->{'rate_flat'}; return ''; }, - 'fieldorder' => [ 'unit_field', 'rate_field', 'rate_flat', 'display_fields' ] + 'fieldorder' => [ 'queueids', 'unit_field', 'rate_field', 'rate_flat', 'display_fields' ] ); sub price_info { my $self = shift; my $str = $self->SUPER::price_info; $str .= ' plus ' if $str; - FS::TicketSystem->init(); - my %custom_fields = FS::TicketSystem->custom_fields(); - my $rate = $self->option('rate_flat',1); - my $rate_field = $self->option('rate_field',1); - my $unit_field = $self->option('unit_field'); - $str .= $rate - ? $money_char . sprintf("%.2",$rate) - : $custom_fields{$rate_field}; - $str .= ' x ' . $custom_fields{$unit_field}; + $str .= 'charge from RT'; +# takes way too long just to get a package label +# FS::TicketSystem->init(); +# my %custom_fields = FS::TicketSystem->custom_fields(); +# my $rate = $self->option('rate_flat',1); +# my $rate_field = $self->option('rate_field',1); +# my $unit_field = $self->option('unit_field'); +# $str .= $rate +# ? $money_char . sprintf("%.2",$rate) +# : $custom_fields{$rate_field}; +# $str .= ' x ' . $custom_fields{$unit_field}; return $str; } @@ -103,11 +113,31 @@ sub calc_usage { FS::TicketSystem->init(); - # not date delimited--load all resolved tickets - # will subtract previous charges below - # only way to be sure we've caught everything - # limit set to be arbitrarily large (10000) - my $tickets = FS::TicketSystem->customer_tickets( $cust_pkg->custnum, 10000, undef, 'resolved'); + my %queues = FS::TicketSystem->queues(undef,'SeeCustomField'); + + my @tickets; + foreach my $queueid ( + split(', ',$self->option('queueids',1) || '') + ) { + + die "Insufficient permission to invoice package" + unless exists $queues{$queueid}; + + # load all resolved tickets since pkg was ordered + # will subtract previous charges below + # only way to be sure we've caught everything + my $tickets = FS::TicketSystem->customer_tickets({ + number => $cust_pkg->custnum, + limit => 10000, # arbitrarily large + status => 'resolved', + queueid => $queueid, + resolved => $cust_pkg->order_date, # or setup? but this is mainly for installations, + # and workflow might resolve tickets before first bill... + # for now, expect pkg to be ordered before tickets get resolved, + # easy enough to make a pkg option to use setup/sdate instead + }); + push @tickets, @$tickets; + }; my $rate = $self->option('rate_flat',1); my $rate_field = $self->option('rate_field',1); @@ -124,7 +154,7 @@ sub calc_usage { $unit_field = 'CF.{' . $unit_field . '}'; my $charges = 0; - foreach my $ticket ( @$tickets ) { + foreach my $ticket ( @tickets ) { next unless $ticket->{$unit_field}; next unless $rate || $ticket->{$rate_field}; my $trate = $rate || $ticket->{$rate_field}; diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index 2c8477d8e..7fe659f94 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -988,9 +988,9 @@ my $html_bottom = sub { : '' ). '>'; - } elsif ( $href->{$field}{'type'} eq 'select-rt-customfield' ) { + } elsif ( $href->{$field}{'type'} =~ /^select-rt-/ ) { - $html .= include('/elements/select-rt-customfield.html', + $html .= include('/elements/'.$href->{$field}{'type'}.'.html', 'name' => $layer.'__'.$field, 'curr_value' => $options{$field}, map { $_ => $href->{$field}{$_} } diff --git a/httemplate/elements/select-rt-queue.html b/httemplate/elements/select-rt-queue.html new file mode 100644 index 000000000..4ae8bc942 --- /dev/null +++ b/httemplate/elements/select-rt-queue.html @@ -0,0 +1,24 @@ + +<%init> +my %opt = @_; + +my %curr_value = map { $_ => 1 } split(', ',$opt{'curr_value'}); + +my @fields; +push @fields, '', $opt{empty_label} if exists($opt{empty_label}); + +my $conf = new FS::Conf; + +if ($conf->config('ticket_system') eq 'RT_Internal') { + + push @fields, FS::TicketSystem->queues(); + +} + + -- 2.11.0