time/data/etc. unit pricing add-ons, RT#24392
authorIvan Kohler <ivan@freeside.biz>
Sun, 5 Jan 2014 07:31:16 +0000 (23:31 -0800)
committerIvan Kohler <ivan@freeside.biz>
Sun, 5 Jan 2014 07:31:16 +0000 (23:31 -0800)
FS/FS.pm
FS/FS/Schema.pm
FS/FS/part_pkg_usageprice.pm [new file with mode: 0644]
httemplate/edit/part_pkg.cgi
httemplate/edit/process/part_pkg.cgi
httemplate/elements/part_pkg_usageprice.html [new file with mode: 0644]
httemplate/elements/tr-part_pkg_usageprice.html [new file with mode: 0644]

index 71a7a2f..3ff3656 100644 (file)
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -255,6 +255,8 @@ L<FS::part_pkg> - Package definition class
 
 L<FS::part_pkg_msgcat> - Package definition localization class
 
 
 L<FS::part_pkg_msgcat> - Package definition localization class
 
+L<FS::part_pkg_usageprice> - Package definition usage pricing add-on class
+
 L<FS::part_pkg_currency> - Package definition local currency prices
 
 L<FS::currency_exchange> - Currency exchange rates
 L<FS::part_pkg_currency> - Package definition local currency prices
 
 L<FS::currency_exchange> - Currency exchange rates
index 73320fa..5833d86 100644 (file)
@@ -2928,7 +2928,7 @@ sub tables_hashref {
                         ],
     },
 
                         ],
     },
 
-   'part_pkg' => {
+    'part_pkg' => {
       'columns' => [
         'pkgpart',       'serial',    '',   '', '', '', 
         'pkg',           'varchar',   '',   $char_d, '', '', 
       'columns' => [
         'pkgpart',       'serial',    '',   '', '', '', 
         'pkg',           'varchar',   '',   $char_d, '', '', 
@@ -3036,6 +3036,26 @@ sub tables_hashref {
       'index'       => [],
     },
 
       'index'       => [],
     },
 
+    'part_pkg_usageprice' => {
+      'columns' => [
+        'usagepricepart', 'serial',      '',      '', '', '',
+        'pkgpart',           'int',      '',      '', '', '',
+        'price',          @money_type,                '', '', 
+        'currency',         'char',  'NULL',       3, '', '',
+        'action',        'varchar',      '', $char_d, '', '',
+        'target',        'varchar',      '', $char_d, '', '',
+        'amount',        'varchar',      '', $char_d, '', '',
+      ],
+      'primary_key'  => 'usagepricepart',
+      'unique'       => [ [ 'pkgpart', 'currency', 'target' ] ],
+      'index'        => [ [ 'pkgpart' ] ],
+      'foreign_keys' => [
+                          { columns    => [ 'pkgpart' ],
+                            table      => 'part_pkg',
+                          },
+                        ],
+    },
+
     'part_pkg_link' => {
       'columns' => [
         'pkglinknum',  'serial',   '',      '', '', '',
     'part_pkg_link' => {
       'columns' => [
         'pkglinknum',  'serial',   '',      '', '', '',
diff --git a/FS/FS/part_pkg_usageprice.pm b/FS/FS/part_pkg_usageprice.pm
new file mode 100644 (file)
index 0000000..0fa6bca
--- /dev/null
@@ -0,0 +1,134 @@
+package FS::part_pkg_usageprice;
+use base qw( FS::Record );
+
+use strict;
+#use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::part_pkg_usageprice - Object methods for part_pkg_usageprice records
+
+=head1 SYNOPSIS
+
+  use FS::part_pkg_usageprice;
+
+  $record = new FS::part_pkg_usageprice \%hash;
+  $record = new FS::part_pkg_usageprice { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_pkg_usageprice object represents a usage pricing add-on.
+FS::part_pkg_usageprice inherits from FS::Record.  The following fields are
+currently supported:
+
+=over 4
+
+=item usagepricepart
+
+primary key
+
+=item pkgpart
+
+pkgpart
+
+=item price
+
+price
+
+=item currency
+
+currency
+
+=item action
+
+action
+
+=item target
+
+target
+
+=item amount
+
+amount
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record.  To add the record 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
+
+sub table { 'part_pkg_usageprice'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=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.
+
+=item check
+
+Checks all fields to make sure this is a valid record.  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('usagepricepart')
+    || $self->ut_number('pkgpart')
+    || $self->ut_money('price')
+    || $self->ut_currencyn('currency')
+    || $self->ut_enum('action', [ 'increment', 'set' ])
+    || $self->ut_enum('target', [ 'svc_acct.totalbytes', 'svc_acct.seconds',
+                                  'svc_conferencing.participants',
+                                  'svc_conferencing.confqualitynum'
+                                ]
+                     )
+    || $self->ut_text('amount')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::part_pkg>, L<FS::Record>
+
+=cut
+
+1;
+
index 7f8a707..23de7c1 100755 (executable)
@@ -59,6 +59,7 @@
                          }
                        $conf->config('currencies')
                    ),
                          }
                        $conf->config('currencies')
                    ),
+                   'usagepricepart'   => ' ',
                    'discountnum'      => 'Offer discounts for longer terms',
                    'bill_dst_pkgpart' => 'Include line item(s) from package',
                    'svc_dst_pkgpart'  => 'Include services of package',
                    'discountnum'      => 'Offer discounts for longer terms',
                    'bill_dst_pkgpart' => 'Include line item(s) from package',
                    'svc_dst_pkgpart'  => 'Include services of package',
 
                    { type => 'columnend' },
 
 
                    { type => 'columnend' },
 
+                   { type     => 'tablebreak-tr-title',
+                     value    => 'Usage pricing add-ons', #better name?  just 'Usage pricing' ?  there's also CDR usage pricing, RADIUS usage pricing, etc :/
+                   },
+                   { 'field'     => 'usagepricepart',
+                     'type'      => 'part_pkg_usageprice',
+                     'o2m_table' => 'part_pkg_usageprice',
+                     'm2_label'  => ' ',
+                     'm2_error_callback' => $usageprice_error_callback,
+                   },
+
                    { 'type'  => $report_option ? 'tablebreak-tr-title'
                                                : 'hidden',
                      'value' => 'Optional report classes',
                    { 'type'  => $report_option ? 'tablebreak-tr-title'
                                                : 'hidden',
                      'value' => 'Optional report classes',
@@ -684,6 +695,28 @@ my $discount_error_callback = sub {
   $cgi->param;
 };
 
   $cgi->param;
 };
 
+my $usageprice_error_callback = sub {
+  my( $cgi, $object ) = @_;
+  map {
+        if ( /^usagepricepart(\d+)_price$/
+               && $cgi->param("usagepricepart$1_price") )
+        {
+          new FS::part_pkg_usageprice {
+            'usagepricepart' => $cgi->param("usagepricepart$1"),
+            'pkgpart'        => $object->pkgpart,
+            'price'          => scalar($cgi->param("usagepricepart$1_price")),
+            #'currency
+            'action'         => scalar($cgi->param("usagepricepart$1_action")),
+            'target'         => scalar($cgi->param("usagepricepart$1_target")),
+            'amount'         => scalar($cgi->param("usagepricepart$1_amount")),
+          };
+        } else {
+          ();
+        }
+      }
+  $cgi->param;
+};
+
 my $m2_error_callback_maker = sub {
   my $link_type = shift; #yay closures
   return sub {
 my $m2_error_callback_maker = sub {
   my $link_type = shift; #yay closures
   return sub {
index db4fcb9..82c4e1e 100755 (executable)
@@ -269,6 +269,10 @@ my @process_o2m = (
     'table'  => 'part_pkg_msgcat',
     'fields' => [qw( locale pkg )],
   },
     'table'  => 'part_pkg_msgcat',
     'fields' => [qw( locale pkg )],
   },
+  {
+    'table'  => 'part_pkg_usageprice',
+    'fields' => [qw( price currency action target amount )],
+  }
 );
 
 </%init>
 );
 
 </%init>
diff --git a/httemplate/elements/part_pkg_usageprice.html b/httemplate/elements/part_pkg_usageprice.html
new file mode 100644 (file)
index 0000000..5663469
--- /dev/null
@@ -0,0 +1,137 @@
+% unless ( $opt{'js_only'} ) {
+
+  <INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>">
+
+  <TABLE STYLE="display:inline">
+    <TR>
+
+%     ###
+%     # price
+%     ###
+      <TD>
+        <TABLE STYLE="display:inline">
+          <TR>
+            <TD>Price</TD>
+            <TD><% $money_char %><INPUT
+              TYPE  = "text"
+              NAME  = "<%$name%>_price"
+              ID    = "<%$id%>_price"
+              VALUE = "<% scalar($cgi->param($name.'_price'))
+                            || $part_pkg_usageprice->price
+                       %>"
+              <% $onchange %>
+            ></TD>
+          </TR>
+
+% #XXX lots more work for multi-currency...  maybe larger UI changes :/
+%         foreach my $currency ( () ) {
+%         #foreach my $currency ( sort $conf->config('currencies') ) {
+            <TR>
+              <TD><% $currency %></TD>
+              <TD><% currency_symbol($currency, SYM_HTML) %><INPUT
+                TYPE  = "text"
+                NAME  = "<%$name%>_price_<%$currency%>"
+                ID    = "<%$id%>_price_<%$currency%>"
+                VALUE = "<% scalar($cgi->param($name.'_price_'.$currency))
+                              || $part_pkg_usageprice->price #XXX
+                         %>"
+                <% $onchange %>
+              ></TD>
+            </TR>
+%         }
+
+        </TABLE>
+      </TD>
+
+%     ###
+%     # action
+%     ###
+      <TD>
+        <SELECT NAME = "<%$name%>_action"
+                ID   = "<%$id%>_action"
+                <% $onchange %>
+        >
+          <OPTION VALUE="increment">Increment
+%#no set yet          <OPTION VALUE="set" <% ($cgi->param($name.'_action') || $part_pkg_usageprice->action) eq 'set' ? 'SELECTED' : '' %>>Set
+        </SELECT>
+      </TD>
+
+%     ###
+%     # target
+%     ###
+      <TD>
+        <SELECT NAME = "<%$name%>_target"
+                ID   = "<%$id%>_target"
+                <% $onchange %>
+        >
+%       foreach my $target (keys %targets) {
+          <OPTION VALUE="<% $target %>"
+                  <% ($cgi->param($name.'_target') || $part_pkg_usageprice->target) eq $target ? 'SELECTED' : '' %>
+          ><% $targets{$target}->{label} %>
+%       }
+      </TD>
+
+%     ###
+%     # amount
+%     ###
+      <TD>
+        <INPUT TYPE = "text"
+               NAME = "<%$name%>_amount"
+               ID   = "<%$id%>_amount"
+               SIZE = 5
+               VALUE = "<% scalar($cgi->param($name.'_amount'))
+                             || $part_pkg_usageprice->amount
+                        %>"
+               <% $onchange %>
+        >
+      </TD>
+
+    </TR>
+  </TABLE>
+
+% }
+<%init>
+
+tie my %targets, 'Tie::IxHash', # once?
+  #'svc_acct.totalbytes' => { label => 'Megabytes',
+  #                           mult  => 1048576,
+  #                         },
+  'svc_acct.totalbytes' => { label => 'Gigabytes',
+                             mult  => 1073741824,
+                           },
+  'svc_acct.seconds' => { label => 'Hours',
+                          mult  => 3600,
+                        },
+  'svc_conferencing.participants' => { label => 'Conference Participants',
+                                       mult  => 1,
+                                     },
+#this will take more work: set action, not increment..
+#  and then value comes from a select, not a text field
+#  'svc_conferencing.confqualitynum' => { label => 'Conference Quality',
+#                                        },
+;
+
+my( %opt ) = @_;
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $name = $opt{'element_name'} || $opt{'field'} || 'usagepricepart';
+my $id = $opt{'id'} || 'usagepricepart';
+
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+
+my $onchange = '';
+if ( $opt{'onchange'} ) {
+  $onchange = $opt{'onchange'};
+  $onchange .= '(this)' unless $onchange =~ /\(\w*\);?$/;
+  $onchange =~ s/\(what\);/\(this\);/g; #ugh, terrible hack.  all onchange
+                                        #callbacks should act the same
+  $onchange = 'onChange="'. $onchange. '"';
+}
+
+my $part_pkg_usageprice = $curr_value
+  ? qsearchs('part_pkg_usageprice', { 'usagepricepart' => $curr_value } )
+  : new FS::part_pkg_usageprice {};
+
+</%init>
diff --git a/httemplate/elements/tr-part_pkg_usageprice.html b/httemplate/elements/tr-part_pkg_usageprice.html
new file mode 100644 (file)
index 0000000..1b78ae4
--- /dev/null
@@ -0,0 +1,24 @@
+%   unless ( $opt{'js_only'} ) {
+
+      <% include('tr-td-label.html', %opt) %>
+        <TD <% $cell_style %>>
+
+%   }
+%
+            <% include( '/elements/part_pkg_usageprice.html', %opt ) %>
+%
+%   unless ( $opt{'js_only'} ) {
+
+        </TD>
+      </TR>
+
+%   }
+<%init>
+
+my( %opt ) = @_;
+
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+#$opt{'label'} ||= 'XXX Something';
+
+</%init>