[freeside-commits] branch FREESIDE_4_BRANCH updated. 65e5b66372bd793286cbb165a639aded3fbea5ae

Jonathan Prykop jonathan at 420.am
Sun Aug 14 17:45:15 PDT 2016


The branch, FREESIDE_4_BRANCH has been updated
       via  65e5b66372bd793286cbb165a639aded3fbea5ae (commit)
       via  4ec2818fa42bf6b3ed00f6888c744456855ec0f1 (commit)
       via  0edd5b9d5c03f02341b1004888f0f712c8defb47 (commit)
      from  630a48c51cc20dd2e2c2a09a61999e5a0ae3e5ba (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 65e5b66372bd793286cbb165a639aded3fbea5ae
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Sun Aug 14 19:11:32 2016 -0500

    RT#38973: Bill for time worked on ticket resolution [validate queueids]

diff --git a/FS/FS/part_pkg/rt_field.pm b/FS/FS/part_pkg/rt_field.pm
index 657a8d7..293bfa7 100644
--- a/FS/FS/part_pkg/rt_field.pm
+++ b/FS/FS/part_pkg/rt_field.pm
@@ -38,6 +38,7 @@ our %info = (
     'queueids'       => { 'name' => 'Queues',
                           'type' => 'select-rt-queue',
                           %multiple,
+                          'validate' => sub { return ${$_[1]} ? '' : 'Queue must be specified' },
                         },
     'unit_field'     => { 'name' => 'Units field',
                           %custom_field,

commit 4ec2818fa42bf6b3ed00f6888c744456855ec0f1
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Mon Jul 18 19:29:59 2016 -0500

    RT#38973: Bill for time worked on ticket resolution [fully functional]

diff --git a/FS/FS/TicketSystem/RT_Internal.pm b/FS/FS/TicketSystem/RT_Internal.pm
index 01806c1..99e7044 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 de88cc0..1ee8552 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
@@ -2631,6 +2632,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 1b18720..657a8d7 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 2c8477d..7fe659f 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 0000000..4ae8bc9
--- /dev/null
+++ b/httemplate/elements/select-rt-queue.html
@@ -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>

commit 0edd5b9d5c03f02341b1004888f0f712c8defb47
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Sat Jul 16 15:43:42 2016 -0500

    RT#38973: Bill for time worked on ticket resolution [checkpoint, not ready for backport]

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 7d92d65..b09266b 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -7438,6 +7438,26 @@ sub tables_hashref {
                         ],
     },
 
+    'rt_field_charge' => {
+      'columns' => [
+        'rtfieldchargenum',    'serial',      '',      '', '', '',
+        'pkgnum',                 'int',      '',      '', '', '', 
+        'ticketid',               'int',      '',      '', '', '', 
+        'rate',             @money_type,                   '', '', 
+        'units',              'decimal',      '',  '10,4', '', '',
+        'charge',           @money_type,                   '', '', 
+        '_date',             @date_type,                   '', '',
+      ],
+      'primary_key'  => 'rtfieldchargenum',
+      'unique'       => [],
+      'index'        => [ ['pkgnum', 'ticketid'] ],
+      'foreign_keys' => [
+                          { columns    => [ 'pkgnum' ],
+                            table      => 'cust_pkg',
+                          },
+                        ],
+    },
+
     # name type nullability length default local
 
     #'new_table' => {
diff --git a/FS/FS/TicketSystem/RT_Internal.pm b/FS/FS/TicketSystem/RT_Internal.pm
index ffee484..01806c1 100644
--- a/FS/FS/TicketSystem/RT_Internal.pm
+++ b/FS/FS/TicketSystem/RT_Internal.pm
@@ -255,7 +255,10 @@ sub _ticket_info {
   }
   $ticket_info{'owner'} = $t->OwnerObj->Name;
   $ticket_info{'queue'} = $t->QueueObj->Name;
+  $ticket_info{'_cf_sort_order'} = {};
+  my $cf_sort = 0;
   foreach my $CF ( @{ $t->CustomFields->ItemsArrayRef } ) {
+    $ticket_info{'_cf_sort_order'}{$CF->Name} = $cf_sort++;
     my $name = 'CF.{'.$CF->Name.'}';
     $ticket_info{$name} = $t->CustomFieldValuesAsString($CF->Id);
   }
@@ -649,5 +652,49 @@ sub selfservice_priority {
   }
 }
 
+=item custom_fields
+
+Returns a hash of custom field names and descriptions.
+
+Accepts the following options:
+
+lookuptype - limit results to this lookuptype
+
+valuetype - limit results to this valuetype
+
+Fields must be visible to CurrentUser.
+
+=cut
+
+sub custom_fields {
+  my $self = shift;
+  my %opt = @_;
+  my $lookuptype = $opt{lookuptype};
+  my $valuetype = $opt{valuetype};
+
+  my $CurrentUser = RT::CurrentUser->new();
+  $CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username);
+  die "RT not configured" unless $CurrentUser->id;
+  my $CFs = RT::CustomFields->new($CurrentUser);
+
+  $CFs->UnLimit;
+
+  $CFs->Limit(FIELD => 'LookupType',
+              OPERATOR => 'ENDSWITH',
+              VALUE => $lookuptype)
+      if $lookuptype;
+
+  $CFs->Limit(FIELD => 'Type',
+              VALUE => $valuetype)
+      if $valuetype;
+
+  my @fields;
+  while (my $CF = $CFs->Next) {
+    push @fields, $CF->Name, ($CF->Description || $CF->Name);
+  }
+
+  return @fields;
+}
+
 1;
 
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
index 709e137..92943f2 100644
--- a/FS/FS/part_pkg.pm
+++ b/FS/FS/part_pkg.pm
@@ -773,8 +773,12 @@ sub check {
 =item check_options
 
 For a passed I<$options> hashref, validates any options that
-have 'validate' subroutines defined (I<$options> values might
-be altered.)  Returns error message, or empty string if valid.
+have 'validate' subroutines defined in the info hash, 
+then validates the entire hashref if the price plan has 
+its own 'validate' subroutine defined in the info hash 
+(I<$options> values might be altered.)  
+
+Returns error message, or empty string if valid.
 
 Invoked by L</insert> and L</replace> via the equivalent
 methods in L<FS::option_Common>.
@@ -793,6 +797,10 @@ sub check_options {
       }
     } # else "option does not exist" error?
   }
+  if (exists($plans{$self->plan}->{'validate'})) {
+    my $error = &{$plans{$self->plan}->{'validate'}}($options);
+    return $error if $error;
+  }
   return '';
 }
 
diff --git a/FS/FS/part_pkg/rt_field.pm b/FS/FS/part_pkg/rt_field.pm
new file mode 100644
index 0000000..1b18720
--- /dev/null
+++ b/FS/FS/part_pkg/rt_field.pm
@@ -0,0 +1,177 @@
+package FS::part_pkg::rt_field;
+
+use strict;
+use FS::Conf;
+use FS::TicketSystem;
+use FS::Record qw(qsearchs qsearch);
+use FS::part_pkg::recur_Common;
+use FS::part_pkg::global_Mixin;
+use FS::rt_field_charge;
+
+our @ISA = qw(FS::part_pkg::recur_Common);
+
+our $DEBUG = 0;
+
+use vars qw( $conf $money_char );
+
+FS::UID->install_callback( sub {
+  $conf = new FS::Conf;
+  $money_char = $conf->config('money_char') || '$';
+});
+
+my %custom_field = (
+  'type'        => 'select-rt-customfield',
+  'lookuptype'  => 'RT::Queue-RT::Ticket',
+);
+
+our %info = (
+  'name'      =>  'Bill from custom fields in resolved RT tickets',
+  'shortname' =>  'RT custom rate',
+  'weight'    => 65,
+  'inherit_fields' => [ 'global_Mixin' ],
+  'fields'    =>  {
+    'unit_field'     => { 'name' => 'Units field',
+                          %custom_field,
+                          'validate' => sub { return ${$_[1]} ? '' : 'Units field must be specified' },
+                        },
+    'rate_field'     => { 'name' => 'Charge per unit (from RT field)',
+                          %custom_field,
+                          'empty_label' => '',
+                        },
+	'rate_flat'      => { 'name' => 'Charge per unit (flat)',
+                          '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
+                        },
+    # from global_Mixin, but don't get used by this at all
+    'unused_credit_cancel'  => {'disabled' => 1},
+    'unused_credit_suspend' => {'disabled' => 1},
+    'unused_credit_change'  => {'disabled' => 1},
+  },
+  'validate' => sub {
+    my $options = shift;
+    return 'Rate must be specified'
+      unless $options->{'rate_field'} or $options->{'rate_flat'};
+    return 'Cannot specify both flat rate and rate field'
+      if $options->{'rate_field'} and $options->{'rate_flat'};
+    return '';
+  },
+  'fieldorder' => [ '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};
+    return $str;
+}
+
+sub calc_setup {
+  my($self, $cust_pkg ) = @_;
+  $self->option('setup_fee');
+}
+
+sub calc_recur {
+  my $self = shift;
+  my($cust_pkg, $sdate, $details, $param ) = @_;
+
+  my $charges = 0;
+
+  $charges += $self->calc_usage(@_);
+  $charges += ($cust_pkg->quantity || 1) * $self->calc_recur_Common(@_);
+
+  $charges;
+
+}
+
+sub can_discount { 0; }
+
+sub calc_usage {
+  my $self = shift;
+  my($cust_pkg, $sdate, $details, $param ) = @_;
+
+  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 $rate = $self->option('rate_flat',1);
+  my $rate_field = $self->option('rate_field',1);
+  my $unit_field = $self->option('unit_field');
+  my @display_fields = split(', ',$self->option('display_fields',1) || '');
+
+  my %custom_fields = FS::TicketSystem->custom_fields();
+  my $rate_label = $rate
+                   ? ''
+                   : ' ' . $custom_fields{$rate_field};
+  my $unit_label = $custom_fields{$unit_field};
+
+  $rate_field = 'CF.{' . $rate_field . '}' if $rate_field;
+  $unit_field = 'CF.{' . $unit_field . '}';
+
+  my $charges = 0;
+  foreach my $ticket ( @$tickets ) {
+    next unless $ticket->{$unit_field};
+    next unless $rate || $ticket->{$rate_field};
+    my $trate = $rate || $ticket->{$rate_field};
+    my $tunit = $ticket->{$unit_field};
+    my $subcharge = sprintf('%.2f', $trate * $tunit);
+    my $precharge = _previous_charges( $cust_pkg->pkgnum, $ticket->{'id'} );
+    $subcharge -= $precharge;
+
+    # if field values for previous charges increased,
+    # we can make additional charges here and now,
+    # but if field values were decreased, we just ignore--
+    # credits will have to be applied manually later, if that's what's intended
+    next if $subcharge <= 0;
+
+    my $rt_field_charge = new FS::rt_field_charge {
+      'pkgnum' => $cust_pkg->pkgnum,
+      'ticketid' => $ticket->{'id'},
+      'rate' => $trate,
+      'units' => $tunit,
+      'charge' => $subcharge,
+      '_date' => $$sdate,
+    };
+    my $error = $rt_field_charge->insert;
+    die "Error inserting rt_field_charge: $error" if $error;
+    push @$details, $money_char . sprintf('%.2f',$trate) . $rate_label . ' x ' . $tunit . ' ' . $unit_label;
+    push @$details, ' - ' . $money_char . sprintf('%.2f',$precharge) . ' previously charged' if $precharge;
+    foreach my $field (
+      sort { $ticket->{'_cf_sort_order'}{$a} <=> $ticket->{'_cf_sort_order'}{$b} } @display_fields
+    ) {
+      my $label = $custom_fields{$field};
+      my $value = $ticket->{'CF.{' . $field . '}'};
+      push @$details, $label . ': ' . $value if $value;
+    }
+    $charges += $subcharge;
+  }
+  return $charges;
+}
+
+sub _previous_charges {
+  my ($pkgnum, $ticketid) = @_;
+  my $prev = 0;
+  foreach my $rt_field_charge (
+    qsearch('rt_field_charge', { pkgnum => $pkgnum, ticketid => $ticketid })
+  ) {
+    $prev += $rt_field_charge->charge;
+  }
+  return $prev;
+}
+
+1;
diff --git a/FS/FS/rt_field_charge.pm b/FS/FS/rt_field_charge.pm
new file mode 100644
index 0000000..fb01f81
--- /dev/null
+++ b/FS/FS/rt_field_charge.pm
@@ -0,0 +1,132 @@
+package FS::rt_field_charge;
+use base qw( FS::Record );
+
+use strict;
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::rt_field_charge - Object methods for rt_field_charge records
+
+=head1 SYNOPSIS
+
+  use FS::rt_field_charge;
+
+  $record = new FS::rt_field_charge \%hash;
+  $record = new FS::rt_field_charge { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::rt_field_charge object represents an individual charge
+that has been added to an invoice by a package with the rt_field price plan.
+FS::rt_field_charge inherits from FS::Record.
+The following fields are currently supported:
+
+=over 4
+
+=item rtfieldchargenum - primary key
+
+=item pkgnum - cust_pkg that generated the charge
+
+=item ticketid - RT ticket that generated the charge
+
+=item rate - the rate per unit for the charge
+
+=item units - quantity of units being charged
+
+=item charge - the total amount charged
+
+=item _date - billing date for the charge
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new object.  To add the object to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'rt_field_charge'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid object.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('rtfieldchargenum')
+    || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum' )
+    || $self->ut_number('ticketid')
+    || $self->ut_money('rate')
+    || $self->ut_float('units')
+    || $self->ut_money('charge')
+    || $self->ut_number('_date')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index f2c4aac..2c8477d 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -988,6 +988,16 @@ my $html_bottom = sub {
                    : ''
                  ). '>';
 
+      } elsif ( $href->{$field}{'type'} eq 'select-rt-customfield' ) {
+
+        $html .= include('/elements/select-rt-customfield.html',
+                           'name'       => $layer.'__'.$field,
+                           'curr_value' => $options{$field},
+                           map { $_ => $href->{$field}{$_} }
+                             grep { $_ !~ /^(name|type|parse)$/ }
+                               keys %{ $href->{$field} }
+                        );
+
       } elsif ( $href->{$field}{'type'} eq 'select-rate' ) {
 
         $html .= include('/elements/select-rate.html',
diff --git a/httemplate/elements/select-rt-customfield.html b/httemplate/elements/select-rt-customfield.html
index 85758d5..488acca 100644
--- a/httemplate/elements/select-rt-customfield.html
+++ b/httemplate/elements/select-rt-customfield.html
@@ -1,31 +1,27 @@
-<SELECT NAME="<% $opt{name} %>">
+<SELECT NAME="<% $opt{'name'} %>"<% $opt{'multiple'} ? ' MULTIPLE' : '' %>>
 % while ( @fields ) {
-<OPTION VALUE="<% shift @fields %>"><% shift @fields %></OPTION>
+%   my $value = shift @fields;
+%   my $label = shift @fields;
+<OPTION VALUE="<% $value %>"<% $curr_value{$value} ? ' SELECTED' : '' %>><% $label %></OPTION>
 % }
 </SELECT>
 <%init>
 my %opt = @_;
-my $lookuptype = $opt{lookuptype};
-my $valuetype = $opt{valuetype};
-# get a list of TimeValue-type custom fields
-my $CurrentUser = RT::CurrentUser->new();
-$CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username);
-die "RT not configured" unless $CurrentUser->id;
-my $CFs = RT::CustomFields->new($CurrentUser);
 
-$CFs->Limit(FIELD => 'LookupType',
-            OPERATOR => 'ENDSWITH',
-            VALUE => $lookuptype)
-    if $lookuptype;
-
-$CFs->Limit(FIELD => 'Type',
-            VALUE => $valuetype)
-    if $valuetype;
+my %curr_value = map { $_ => 1 } split(', ',$opt{'curr_value'});
 
 my @fields;
 push @fields, '', $opt{empty_label} if exists($opt{empty_label});
 
-while (my $CF = $CFs->Next) {
-  push @fields, $CF->Name, ($CF->Description || $CF->Name);
+my $conf = new FS::Conf;
+
+if ($conf->config('ticket_system') eq 'RT_Internal') {
+
+  push @fields, FS::TicketSystem->custom_fields(
+    lookuptype => $opt{lookuptype},
+    valuetype  => $opt{valuetype},
+  );
+
 }
+
 </%init>

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/Schema.pm                                    |   20 ++
 FS/FS/TicketSystem/RT_Internal.pm                  |   84 +++++++-
 FS/FS/cust_pkg.pm                                  |   14 ++
 FS/FS/part_pkg.pm                                  |   12 +-
 FS/FS/part_pkg/rt_field.pm                         |  208 ++++++++++++++++++++
 ...s_srvderive_component.pm => rt_field_charge.pm} |   52 ++---
 httemplate/edit/part_pkg.cgi                       |   10 +
 httemplate/elements/select-rt-customfield.html     |   34 ++--
 httemplate/elements/select-rt-queue.html           |   24 +++
 9 files changed, 411 insertions(+), 47 deletions(-)
 create mode 100644 FS/FS/part_pkg/rt_field.pm
 copy FS/FS/{torrus_srvderive_component.pm => rt_field_charge.pm} (53%)
 create mode 100644 httemplate/elements/select-rt-queue.html




More information about the freeside-commits mailing list