start of settlement CDR processing, RT#5495
authorivan <ivan>
Wed, 1 Jul 2009 10:28:49 +0000 (10:28 +0000)
committerivan <ivan>
Wed, 1 Jul 2009 10:28:49 +0000 (10:28 +0000)
FS/FS/Schema.pm
FS/FS/cdr_termination.pm [new file with mode: 0644]
FS/FS/cust_main.pm
FS/FS/part_pkg/cdr_termination.pm [new file with mode: 0644]
FS/FS/part_pkg/recur_Common.pm [new file with mode: 0644]
FS/MANIFEST
FS/t/cdr_termination.t [new file with mode: 0644]
httemplate/edit/cust_main/billing.html
httemplate/view/cust_main/billing.html

index 048fa0a..1ae3719 100644 (file)
@@ -694,6 +694,7 @@ sub tables_hashref {
         'comments', 'text', 'NULL', '', '', '', 
         'spool_cdr','char', 'NULL', 1, '', '', 
         'squelch_cdr','char', 'NULL', 1, '', '', 
+        'cdr_termination_percentage', 'decimal', 'NULL', '', '', '',
         'invoice_terms', 'varchar', 'NULL', $char_d, '', '',
         'archived', 'char', 'NULL', 1, '', '',
       ],
@@ -2144,6 +2145,32 @@ sub tables_hashref {
       'index' => [ [ 'calldate' ], [ 'src' ], [ 'dst' ], [ 'charged_party' ], [ 'accountcode' ], [ 'freesidestatus' ], [ 'freesiderewritestatus' ], [ 'cdrbatch' ], ],
     },
 
+    'cdr_termination' => {
+      'columns' => [
+        'cdrtermnum', 'bigserial', '',   '', '', '',
+        'acctid',        'bigint', '',   '', '', '', 
+        'termpart',         'int', '',   '', '', '',#future expansion, see below
+        'rated_price',  @money_typen,        '', '',
+        'status',     'varchar', 'NULL', 32, '', '',
+      ],
+      'primary_key' => 'cdrtermnum',
+      'unique'      => [ [ 'acctid', 'termpart' ] ],
+      'index'       => [ [ 'acctid' ], [ 'status' ], ],
+    },
+
+    #to handle multiple termination/settlement passes...
+   # 'part_termination' => {
+   #   'columns' => [
+   #     'termpart',       'int', '',      '', '', '',
+   #     'termname',   'varchar', '', $char_d, '', '',
+   #     'cdr_column', 'varchar', '', $char_d, '', '', #maybe set it here instead of in the price plan?
+   #   ],
+   #   'primary_key' => 'termpart',
+   #   'unique' => [],
+   #   'index'  => [],
+   # },
+
+    #the remaining cdr_ tables are not really used
     'cdr_calltype' => {
       'columns' => [
         'calltypenum',   'serial',  '', '', '', '', 
diff --git a/FS/FS/cdr_termination.pm b/FS/FS/cdr_termination.pm
new file mode 100644 (file)
index 0000000..5fe8db6
--- /dev/null
@@ -0,0 +1,152 @@
+package FS::cdr_termination;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::cdr_termination - Object methods for cdr_termination records
+
+=head1 SYNOPSIS
+
+  use FS::cdr_termination;
+
+  $record = new FS::cdr_termination \%hash;
+  $record = new FS::cdr_termination { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cdr_termination object represents an CDR termination status.
+FS::cdr_termination inherits from FS::Record.  The following fields are
+currently supported:
+
+=over 4
+
+=item cdrtermnum
+
+primary key
+
+=item acctid
+
+acctid
+
+=item termpart
+
+termpart
+
+=item rated_price
+
+rated_price
+
+=item status
+
+status
+
+
+=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
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'cdr_termination'; }
+
+=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 record.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('cdrtermnum')
+    || $self->ut_foreign_key('acctid', 'cdr', 'acctid')
+    #|| $self->ut_foreign_key('termpart', 'part_termination', 'termpart')
+    || $self->ut_number('termpart')
+    || $self->ut_float('rated_price')
+    || $self->ut_enum('status', '', 'done' ) # , 'skipped' )
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item set_status_and_rated_price STATUS [ RATED_PRICE ]
+
+Sets the status to the provided string.  If there is an error, returns the
+error, otherwise returns false.
+
+=cut
+
+sub set_status_and_rated_price {
+  my($self, $status, $rated_price) = @_;
+  $self->status($status);
+  $self->rated_price($rated_price);
+  $self->replace();
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cdr>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
index 5768beb..1e43519 100644 (file)
@@ -1553,6 +1553,7 @@ sub check {
     || $self->ut_textn('stateid_state')
     || $self->ut_textn('invoice_terms')
     || $self->ut_alphan('geocode')
+    || $self->ut_floatn('cdr_termination_percentage')
   ;
 
   #barf.  need message catalogs.  i18n.  etc.
@@ -6739,11 +6740,38 @@ sub credit {
 
 }
 
-=item charge AMOUNT [ PKG [ COMMENT [ TAXCLASS ] ] ]
+=item charge HASHREF || AMOUNT [ PKG [ COMMENT [ TAXCLASS ] ] ]
 
 Creates a one-time charge for this customer.  If there is an error, returns
 the error, otherwise returns false.
 
+New-style, with a hashref of options:
+
+  my $error = $cust_main->charge(
+                                  {
+                                    'amount'     => 54.32,
+                                    'quantity'   => 1,
+                                    'start_date' => str2time('7/4/2009'),
+                                    'pkg'        => 'Description',
+                                    'comment'    => 'Comment',
+                                    'additional' => [], #extra invoice detail
+                                    'classnum'   => 1,  #pkg_class
+
+                                    'setuptax'   => '', # or 'Y' for tax exempt
+
+                                    #internal taxation
+                                    'taxclass'   => 'Tax class',
+
+                                    #vendor taxation
+                                    'taxproduct' => 2,  #part_pkg_taxproduct
+                                    'override'   => {}, #XXX describe
+                                  }
+                                );
+
+Old-style:
+
+  my $error = $cust_main->charge( 54.32, 'Description', 'Comment', 'Tax class' );
+
 =cut
 
 sub charge {
diff --git a/FS/FS/part_pkg/cdr_termination.pm b/FS/FS/part_pkg/cdr_termination.pm
new file mode 100644 (file)
index 0000000..c0d99b7
--- /dev/null
@@ -0,0 +1,96 @@
+package FS::part_pkg::cdr_termination;
+
+use strict;
+use base qw( FS::part_pkg::recur_Common );
+use vars qw( $DEBUG %info );
+use Tie::IxHash;
+
+tie my %temporalities, 'Tie::IxHash',
+  'upcoming'  => "Upcoming (future)",
+  'preceding' => "Preceding (past)",
+;
+
+%info = (
+  'name' => 'VoIP rating of CDR records for termination partners.',
+  'shortname' => 'VoIP/telco CDR termination',
+  'fields' => {
+
+    'setup_fee'     => { 'name' => 'Setup fee for this package',
+                         'default' => 0,
+                       },
+    'recur_fee'     => { 'name' => 'Base recurring fee for this package',
+                         'default' => 0,
+                       },
+
+    #false laziness w/flat.pm
+    'recur_temporality' => { 'name' => 'Charge recurring fee for period',
+                             'type' => 'select',
+                             'select_options' => \%temporalities,
+                           },
+
+    'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+                                   ' of service at cancellation',
+                         'type' => 'checkbox',
+                       },
+
+    'cutoff_day'    => { 'name' => 'Billing Day (1 - 28) for prorating or '.
+                                   'subscription',
+                         'default' => '1',
+                       },
+
+    'recur_method'  => { 'name' => 'Recurring fee method',
+                         #'type' => 'radio',
+                         #'options' => \%recur_method,
+                         'type' => 'select',
+                         'select_options' => \%FS::part_pkg::recur_Common::recur_method,
+                       },
+  },
+
+  'fieldorder' => [qw(
+                       setup_fee recur_fee recur_temporality unused_credit
+                       recur_method cutoff_day
+                     )
+                  ],
+
+  'weight' => 48,
+
+);
+
+sub calc_setup {
+  my($self, $cust_pkg ) = @_;
+  $self->option('setup_fee');
+}
+
+sub calc_recur {
+  my $self = shift;
+  my($cust_pkg, $sdate, $details, $param ) = @_;
+
+  #my $last_bill = $cust_pkg->last_bill;
+  my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup
+
+  return 0
+    if $self->option('recur_temporality', 1) eq 'preceding'
+    && ( $last_bill eq '' || $last_bill == 0 );
+
+  my $charges = 0;
+
+  # termination calculations
+
+  # find CDRs with cdr_termination.status NULL
+  #  and matching our customer via svc_external.id/title?  (and via what field?)
+
+  #for each cdr, set status and rated price and add the charges, and add a line
+  #to the invoice
+
+  # eotermiation calculation
+
+  $charges += $self->calc_recur_Common(@_);
+
+  $charges;
+}
+
+sub is_free {
+  0;
+}
+
+1;
diff --git a/FS/FS/part_pkg/recur_Common.pm b/FS/FS/part_pkg/recur_Common.pm
new file mode 100644 (file)
index 0000000..71f3af7
--- /dev/null
@@ -0,0 +1,57 @@
+package FS::part_pkg::recur_Common;
+
+use strict;
+use vars qw( @ISA %recur_method );
+use Tie::IxHash;
+use Time::Local;
+use FS::part_pkg::prorate;
+
+@ISA = qw(FS::part_pkg::prorate);
+
+tie %recur_method, 'Tie::IxHash',
+  'anniversary'  => 'Charge the recurring fee at the frequency specified above',
+  'prorate'      => 'Charge a prorated fee the first time (selectable billing date)',
+  'subscription' => 'Charge the full fee for the first partial period (selectable billing date)',
+;
+
+sub calc_recur_Common {
+  my $self = shift;
+  my($cust_pkg, $sdate, $details, $param ) = @_; #only need $sdate & $param
+
+  my $charges = 0;
+
+  if ( $param->{'increment_next_bill'} ) {
+
+    my $recur_method = $self->option('recur_method', 1) || 'anniversary';
+                  
+    if ( $recur_method eq 'prorate' ) {
+
+      $charges = $self->SUPER::calc_recur(@_);
+
+    } else {
+
+      $charges = $self->option('recur_fee');
+
+      if ( $recur_method eq 'subscription' ) {
+
+        my $cutoff_day = $self->option('cutoff_day', 1) || 1;
+        my ($day, $mon, $year) = ( localtime($$sdate) )[ 3..5 ];
+
+        if ( $day < $cutoff_day ) {
+          if ( $mon == 0 ) { $mon=11; $year--; }
+          else { $mon--; }
+        }
+
+        $$sdate = timelocal(0, 0, 0, $cutoff_day, $mon, $year);
+
+      }#$recur_method eq 'subscription'
+
+    }#$recur_method eq 'prorate'
+
+  }#increment_next_bill
+
+  $charges;
+
+}
+
+1;
index 732e3de..57cbc49 100644 (file)
@@ -449,3 +449,5 @@ FS/phone_device.pm
 t/phone_device.t
 FS/part_device.pm
 t/part_device.t
+FS/cdr_termination.pm
+t/cdr_termination.t
diff --git a/FS/t/cdr_termination.t b/FS/t/cdr_termination.t
new file mode 100644 (file)
index 0000000..7167bf2
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cdr_termination;
+$loaded=1;
+print "ok 1\n";
index f0d9b85..4c4be23 100644 (file)
        <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="spool_cdr" VALUE="Y" <% $cust_main->spool_cdr eq "Y" ? 'CHECKED' : '' %>> Spool CDRs</TD>
       </TR>
 % } else { 
-
       <INPUT TYPE="hidden" NAME="spool_cdr" VALUE="<% $cust_main->spool_cdr %>">
-% } 
+% }
 
 % if ( $conf->exists('voip-cust_cdr_squelch') ) { 
       <TR>
        <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="squelch_cdr" VALUE="Y" <% $cust_main->squelch_cdr eq "Y" ? 'CHECKED' : '' %>> Omit CDRs from invoices</TD>
       </TR>
 % } else { 
-
       <INPUT TYPE="hidden" NAME="squelch_cdr" VALUE="<% $cust_main->squelch_cdr %>">
-% } 
+% }
+
+
+% if ( $show_term || $cust_main->cdr_termination_percentage ) {
+      <TR>
+       <TD ALIGN="right">CDR termination settlement</TD>
+       <TD><INPUT TYPE  = "text"
+                  NAME  = "cdr_termination_percentage"
+                  SIZE  = 6
+                  VALUE = "<% $cust_main->cdr_termination_percentage %>"
+                  STYLE = "text-align:right;"
+        ><B>%</B></TD>
+      </TR>
+% } else {
+      <INPUT TYPE="hidden" NAME="cdr_termination_percentage" VALUE="<% $cust_main->cdr_termination_percentage %>">
+% }
 
   </TABLE>
 
@@ -464,4 +477,10 @@ my @payby = grep /\w/, $conf->config('payby');
 @payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
   unless @payby;
 
+#false laziness w/view/cust_main/billing.html
+my $term_sql = "SELECT COUNT(*) FROM cust_pkg LEFT JOIN part_pkg USING ( pkgpart ) WHERE custnum = ? AND plan = 'cdr_termination' LIMIT 1";
+my $term_sth = dbh->prepare($term_sql)  or die dbh->errstr;
+$term_sth->execute($cust_main->custnum) or die $term_sth->errstr;
+my $show_term = $term_sth->fetchrow_arrayref->[0];
+
 </%init>
index e02c048..049461d 100644 (file)
@@ -216,6 +216,13 @@ Billing information
   </TR>
 % } 
 
+% if ( $show_term || $cust_main->cdr_termination_percentage ) {
+  <TR>
+    <TD ALIGN="right">CDR termination settlement</TD>
+    <TD BGCOLOR="#ffffff"><% $cust_main->cdr_termination_percentage %><% $cust_main->cdr_termination_percentage =~ /\d/ ? '%' : '' %></TD>
+  </TR>
+% }
+
 </TABLE></TD></TR></TABLE>
 <%once>
 
@@ -230,4 +237,10 @@ my @invoicing_list = $cust_main->invoicing_list;
 my $conf = new FS::Conf;
 my $money_char = $conf->config('money_char') || '$';
 
+#false laziness w/edit/cust_main/billing.html
+my $term_sql = "SELECT COUNT(*) FROM cust_pkg LEFT JOIN part_pkg USING ( pkgpart ) WHERE custnum = ? AND plan = 'cdr_termination' LIMIT 1";
+my $term_sth = dbh->prepare($term_sql)  or die dbh->errstr;
+$term_sth->execute($cust_main->custnum) or die $term_sth->errstr;
+my $show_term = $term_sth->fetchrow_arrayref->[0];
+
 </%init>