RT#38973: Bill for time worked on ticket resolution [fully functional, v3 reconcile]
authorJonathan Prykop <jonathan@freeside.biz>
Tue, 19 Jul 2016 00:29:59 +0000 (19:29 -0500)
committerJonathan Prykop <jonathan@freeside.biz>
Wed, 24 Aug 2016 02:15:28 +0000 (21:15 -0500)
FS/FS/TicketSystem/RT_Internal.pm
FS/FS/cust_pkg.pm
FS/FS/part_pkg/rt_field.pm
httemplate/edit/part_pkg.cgi
httemplate/elements/select-rt-queue.html [new file with mode: 0644]

index e269e71..d858127 100644 (file)
@@ -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);
 
index e0e710e..101dd81 100644 (file)
@@ -2593,6 +2593,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( 
index 1b18720..657a8d7 100644 (file)
@@ -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};
index 7b1e864..84e74f6 100755 (executable)
@@ -867,9 +867,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 (file)
index 0000000..4ae8bc9
--- /dev/null
@@ -0,0 +1,24 @@
+<SELECT NAME="<% $opt{'name'} %>"<% $opt{'multiple'} ? ' MULTIPLE' : '' %>>
+% while ( @fields ) {
+%   my $value = shift @fields;
+%   my $label = shift @fields;
+<OPTION VALUE="<% $value %>"<% $curr_value{$value} ? ' SELECTED' : '' %>><% $label %></OPTION>
+% }
+</SELECT>
+<%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();
+
+}
+
+</%init>