[freeside-commits] freeside/FS/FS Conf.pm, 1.378, 1.378.2.1 Mason.pm, 1.47, 1.47.2.1 Schema.pm, 1.226, 1.226.2.1 cust_bill.pm, 1.288, 1.288.2.1 cust_bill_pkg.pm, 1.51, 1.51.2.1 cust_bill_pkg_detail.pm, 1.13, 1.13.2.1 cust_main.pm, 1.527, 1.527.2.1 elec_general.pm, NONE, 1.1.2.1 part_pkg.pm, 1.105, 1.105.2.1 svc_elec.pm, NONE, 1.1.2.1 transaction810.pm, NONE, 1.1.2.1 transaction867.pm, NONE, 1.1.2.1 usage_elec.pm, NONE, 1.1.2.1 usage_elec_transaction867.pm, NONE, 1.1.2.1

Jeff Finucane,420,, jeff at wavetail.420.am
Mon Aug 2 12:49:21 PDT 2010


Update of /home/cvs/cvsroot/freeside/FS/FS
In directory wavetail.420.am:/tmp/cvs-serv14262/FS/FS

Modified Files:
      Tag: svc_elec_features
	Conf.pm Mason.pm Schema.pm cust_bill.pm cust_bill_pkg.pm 
	cust_bill_pkg_detail.pm cust_main.pm part_pkg.pm 
Added Files:
      Tag: svc_elec_features
	elec_general.pm svc_elec.pm transaction810.pm 
	transaction867.pm usage_elec.pm usage_elec_transaction867.pm 
Log Message:
add svc_elec_features merged from reference code RT#7643

Index: cust_bill_pkg_detail.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_bill_pkg_detail.pm,v
retrieving revision 1.13
retrieving revision 1.13.2.1
diff -u -w -d -r1.13 -r1.13.2.1
--- cust_bill_pkg_detail.pm	27 Mar 2010 06:21:53 -0000	1.13
+++ cust_bill_pkg_detail.pm	2 Aug 2010 19:49:18 -0000	1.13.2.1
@@ -57,6 +57,45 @@
 
 =item detail - detail description
 
+=item prev_date
+
+=item curr_date -
+
+=item prev_read -
+
+=item curr_read -
+
+=item tdsp -
+
+=item taxes -
+
+=item rate -
+
+=item gr_fee -
+
+=item energy_base -
+
+=item energy_charge -
+
+=item setup_fee -
+
+=item one_time_charge -
+
+=item one_time_description -
+
+=item balance -
+
+=item last_pay -
+
+=item last_pay_date -
+
+=item return_addr -
+
+=item bill_return_address -
+
+=item pkg_info -
+
+
 =back
 
 =head1 METHODS
@@ -141,6 +180,32 @@
     || $self->ut_foreign_keyn('classnum', 'usage_class', 'classnum')
     || $self->$phonenum_check_method('phonenum')
     || $self->SUPER::check
+    || $self->ut_numbern('prev_date')
+    || $self->ut_numbern('curr_date')
+    || $self->ut_floatn('prev_read')
+    || $self->ut_floatn('curr_read')
+    || $self->ut_money('tdsp')
+    || $self->ut_money('taxes')
+    || $self->ut_money('gr_fee')
+    || $self->ut_money('energy_base')
+    || $self->ut_money('energy_charge')
+    || $self->ut_money('setup_fee')
+    || $self->ut_money('one_time_charge')
+    || $self->ut_floatn('rate')
+    || $self->ut_floatn('discount1_rate')
+    || $self->ut_floatn('discount1_total')
+    || $self->ut_numbern('number_of_days')
+    || $self->ut_floatn('average_price')
+    || $self->ut_floatn('energy_usage')
+    || $self->ut_anything('one_time_description')
+    || $self->ut_money('balance')
+    || $self->ut_money('last_pay')
+    || $self->ut_numbern('last_pay_date')
+    || $self->ut_anything('return_addr')
+    || $self->ut_textn('bill_return_address')
+    || $self->ut_floatn('meter_multiplier')
+    || $self->ut_floatn('demanded_bill')
+    || $self->ut_floatn('measured_bill')
     ;
 
 }
@@ -325,6 +390,7 @@
                          'hashref' => {},
                          'extra_sql' => 'WHERE invnum IS NOT NULL AND '.
                                         'pkgnum IS NOT NULL',
+                         'order_by' => 'ORDER BY detailnum',
                       });
 
     if (scalar(@cbpd)) {

Index: Conf.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/Conf.pm,v
retrieving revision 1.378
retrieving revision 1.378.2.1
diff -u -w -d -r1.378 -r1.378.2.1
--- Conf.pm	30 Jul 2010 22:08:54 -0000	1.378
+++ Conf.pm	2 Aug 2010 19:49:16 -0000	1.378.2.1
@@ -1156,6 +1156,13 @@
   },
 
   {
+    'key'         => 'rec_latex',
+    'section'     => 'invoicing',
+    'description' => 'Optional LaTeX template for typeset PostScript statements when svc_elec_features are enabled.  See the <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Typeset_.28LaTeX.29_invoice_templates">billing documentation</a> for details.',
+    'type'        => 'textarea',
+  },
+
+  {
     'key'         => 'invoice_email_pdf',
     'section'     => 'invoicing',
     'description' => 'Send PDF invoice as an attachment to emailed invoices.  By default, includes the plain text invoice as the email body, unless invoice_email_pdf_note is set.',
@@ -1181,7 +1188,7 @@
     'section'     => 'invoicing',
     'description' => 'Optional default invoice term, used to calculate a due date printed on invoices.',
     'type'        => 'select',
-    'select_enum' => [ '', 'Payable upon receipt', 'Net 0', 'Net 10', 'Net 15', 'Net 20', 'Net 30', 'Net 45', 'Net 60' ],
+    'select_enum' => [ '', 'Payable upon receipt', 'Net 0', 'Net 10', 'Net 15', 'Net 16', 'Net 20', 'Net 30', 'Net 45', 'Net 60' ],
   },
 
   { 
@@ -3885,6 +3892,14 @@
   },
 
   {
+    'key'         => 'svc_elec_features',
+    'section'     => '',
+    'description' => 'Enable electrical billing features',
+    'type'        => 'select',
+    'type'        => 'checkbox',
+  },
+
+  {
     'key'         => 'maestro-status_test',
     'section'     => 'UI',
     'description' => 'Display a link to the maestro status test page on the customer view page',

Index: cust_bill_pkg.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_bill_pkg.pm,v
retrieving revision 1.51
retrieving revision 1.51.2.1
diff -u -w -d -r1.51 -r1.51.2.1
--- cust_bill_pkg.pm	27 Jun 2010 09:25:26 -0000	1.51
+++ cust_bill_pkg.pm	2 Aug 2010 19:49:18 -0000	1.51.2.1
@@ -145,7 +145,12 @@
 
   if ( $self->get('details') ) {
     foreach my $detail ( @{$self->get('details')} ) {
-      my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail {
+      my $cust_bill_pkg_detail;
+      if (ref($detail) eq 'FS::cust_bill_pkg_detail') {
+        $cust_bill_pkg_detail = $detail;
+        $cust_bill_pkg_detail->billpkgnum($self->billpkgnum);
+      } else {
+        $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail {
         'billpkgnum' => $self->billpkgnum,
         'format'     => (ref($detail) ? $detail->[0] : '' ),
         'detail'     => (ref($detail) ? $detail->[1] : $detail ),
@@ -155,6 +160,7 @@
         'duration'   => (ref($detail) ? $detail->[5] : '' ),
         'regionname' => (ref($detail) ? $detail->[6] : '' ),
       };
+      }
       $error = $cust_bill_pkg_detail->insert;
       if ( $error ) {
         $dbh->rollback if $oldAutoCommit;
@@ -870,7 +876,11 @@
   my %hash = ( 'billpkgnum' => $self->billpkgnum );
   $hash{classnum} = $classnum if $classnum;
 
-  qsearch ( 'cust_bill_pkg_detail', { %hash  } ),
+  qsearch ({
+             'table'    => 'cust_bill_pkg_detail',
+             'hashref'  => { %hash  },
+             'order_by' => 'ORDER BY detailnum',
+          });
 
 }
 

Index: cust_bill.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_bill.pm,v
retrieving revision 1.288
retrieving revision 1.288.2.1
diff -u -w -d -r1.288 -r1.288.2.1
--- cust_bill.pm	28 Jun 2010 04:11:47 -0000	1.288
+++ cust_bill.pm	2 Aug 2010 19:49:17 -0000	1.288.2.1
@@ -36,6 +36,8 @@
 use FS::payby;
 use FS::bill_batch;
 use FS::cust_bill_batch;
+use FS::usage_elec qw(most_current_date);
+use FS::cust_bill_pkg_detail; #ugh
 
 @ISA = qw( FS::cust_main_Mixin FS::Record );
 
@@ -50,6 +52,13 @@
   $rdate_format = $conf->config('date_format') || '%m/%d/%Y';  
 } );
 
+# i think this is cruft
+sub usage_elec{
+  warn "$me: usage_elec has been called\n";
+  my $self = shift;
+  qsearch('usage_elec',{ 'cust_nr' => $self->custnum});
+}
+
 =head1 NAME
 
 FS::cust_bill - Object methods for cust_bill records
@@ -2051,7 +2060,7 @@
   $params{'time'} = $today if $today;
   $params{'template'} = $template if $template;
   $params{$_} = $opt{$_} 
-    foreach grep $opt{$_}, qw( unsquealch_cdr notice_name );
+    foreach grep $opt{$_}, qw(unsquealch_cdr notice_name base ignore_due_date);
 
   $template ||= $self->_agent_template;
 
@@ -2100,6 +2109,7 @@
 
 Optional options include
 
+base - a value used for the name of the template. defaults to 'invoice'
 template - a value used as a suffix for a configuration template
 
 time - a value used to control the printing of overdue messages.  The
@@ -2129,6 +2139,15 @@
   die "Unknown format: $format"
     unless $format =~ /^(latex|html|template)$/;
 
+  # this weirdness switches to the most recent invoice under some circumstances
+  if ( $conf->exists('svc_elec_features') && ($params{base} =~ /^rec/i) ) {
+    $self = qsearchs({
+      'table' => 'cust_bill',
+      'hashref' => { 'custnum' => $self->custnum },
+      'order_by' => 'ORDER BY invnum DESC LIMIT 1',
+    });
+  }
+
   my $cust_main = $self->cust_main;
   $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
     unless $cust_main->payname
@@ -2141,7 +2160,8 @@
 
   #create the template
   my $template = $params{template} ? $params{template} : $self->_agent_template;
-  my $templatefile = "invoice_$format";
+  my $templatefile = $params{base} || 'invoice'; #base only used for 'rec'
+  $templatefile .= "_$format";
   $templatefile .= "_$template"
     if length($template);
   my @invoice_template = map "$_\n", $conf->config($templatefile)
@@ -2343,9 +2363,12 @@
     'notice_name'     => ($params{'notice_name'} || 'Invoice'),#escape_function?
     'current_charges' => sprintf("%.2f", $self->charged),
     'duedate'         => $self->due_date2str($rdate_format), #date_format?
+    'due_date'        => $self->due_date2str($rdate_format), #date_format?
+    'ignore_due_date' => ($params{'ignore_due_date'} || ''),
 
     #customer info
     'custnum'         => $cust_main->display_custnum,
+    'phone'           => $cust_main->daytime,
     'agent_custid'    => &$escape_function($cust_main->agent_custid),
     ( map { $_ => &$escape_function($cust_main->$_()) } qw(
       payname company address1 address2 city state zip fax
@@ -2380,6 +2403,121 @@
 
   );
 
+  my @last_cust_bill_pkg_details = ();
+  if ($conf->exists('svc_elec_features')) {
+
+    $invoice_data{date} = time2str('%D', $self->_date);  # date_format?
+
+    # get the detail records sorted by detailnum
+    # too inefficient? 
+    @last_cust_bill_pkg_details =
+      sort { $a->detailnum <=> $b->detailnum }
+      map { $_->cust_bill_pkg_detail }
+      $self->cust_bill_pkg;
+
+    # save a copy of  the last if there is one
+    my $last_cust_bill_pkg_detail;
+    if (scalar(@last_cust_bill_pkg_details)) {
+      $last_cust_bill_pkg_detail = pop @last_cust_bill_pkg_details;
+      push @last_cust_bill_pkg_details, $last_cust_bill_pkg_detail;
+    }
+
+    foreach my $method ( qw( last_pay setup_fee prev_read one_time_charge
+                             curr_read energy_charge energy_base tdsp gr_fee
+                             taxes esiid late_fee average_price 
+                             meter_multplier meter_number ) )
+    {
+      $invoice_data{$method} = $last_cust_bill_pkg_detail
+                               ? $last_cust_bill_pkg_detail->$method
+                               : '';
+    }
+
+    foreach my $method ( qw( prev_date curr_date last_pay_date ) )
+    {
+      $invoice_data{$method} =
+        $last_cust_bill_pkg_detail
+          ? time2str('%D', $last_cust_bill_pkg_detail->$method)
+          : '';
+    }
+
+    foreach my $method ( qw( one_time_description pkg_info note ) )
+    {
+      $invoice_data{$method} =
+        $last_cust_bill_pkg_detail
+          ? &$escape_function($last_cust_bill_pkg_detail->$method)
+          : '';
+    }
+
+    $invoice_data{$_} = ''
+      foreach qw( discount2_total discount2_description discount2_pkgnum
+                  bill_return_address usage numberOfDays balance rate
+                  previousbill_numberOfDays previousbill_totalUsage
+                  lastyear_numberOfDays lastyear_totalUsage
+                  billed_demand measured_demand );
+
+    if ($last_cust_bill_pkg_detail) {
+      $invoice_data{bill_return_address} =
+        $last_cust_bill_pkg_detail->bill_return_addr;
+      $invoice_data{usage} = $last_cust_bill_pkg_detail->energy_usage;
+      $invoice_data{numberOfDays} = $last_cust_bill_pkg_detail->number_of_days;
+      $invoice_data{balance} =
+        sprintf("%.2f", $last_cust_bill_pkg_detail->balance);
+      $invoice_data{actual_balance} = sprintf("%.2f", $cust_main->balance);
+      $invoice_data{rate} = sprintf("%.6f", $last_cust_bill_pkg_detail->rate);
+      $invoice_data{amount_due} =
+        sprintf("%.2f", $self->charged + $last_cust_bill_pkg_detail->balance);
+      $invoice_data{bill_charged} = $invoice_data{current_charges};
+      $invoice_data{billed_demand} = $last_cust_bill_pkg_detail->demanded_bill;
+      $invoice_data{measured_demand} =
+        $last_cust_bill_pkg_detail->measured_bill;
+      $invoice_data{total_discount1} =
+        sprintf('%.2f', $last_cust_bill_pkg_detail->discount1_total)
+        if  $last_cust_bill_pkg_detail->discount1_total
+    }
+
+    if (scalar(@last_cust_bill_pkg_details) > 1) {
+      $invoice_data{previousbill_numberOfDays} =
+        &$escape_function($last_cust_bill_pkg_details[1]->number_of_days);
+      $invoice_data{previousbill_totalUsage} =
+        &$escape_function($last_cust_bill_pkg_details[1]->energy_usage);
+    }
+
+    if (scalar(@last_cust_bill_pkg_details) > 11) {
+      $invoice_data{lastyear_numberOfDays} =
+        &$escape_function($last_cust_bill_pkg_details[11]->number_of_days);
+      $invoice_data{lastyear_totalUsage} =
+        &$escape_function($last_cust_bill_pkg_details[11]->energy_usage);
+    }
+
+    #-ctran 4/11/07 : Manipulatinng the Service address to be input
+    #into latex pdf invoice.  The database table cust_main call the
+    #service address as ship address.  Here I will combine the ship_address1,
+    #ship_address2, and ship_zip to form service address.
+    #-ctran 4/15/07 : If service address is empty, use address1 form cust_main,
+    #this is the mailing address.
+
+    my ($ship_addr1, $ship_addr2) = ($cust_main->ship_address1,
+                                     $cust_main->ship_address2);
+    $ship_addr1 .= ", $ship_addr2" if $ship_addr2;
+
+    # we have a total of 30 character for the service address location,
+    # so address will consist 19 chars, zip 9 chars, ', ' 2 chars = 30
+
+    my $service_addrs;
+    if ($ship_addr1) {
+      if ( (length($ship_addr1)) > 30 ) {
+        $service_addrs = substr($ship_addr1,0,28) . "...";
+      } else {
+        $service_addrs = $ship_addr1;
+      }
+      $service_addrs .= ", ".$cust_main->ship_zip if ($cust_main->ship_zip);
+    } else {
+      $service_addrs = substr($cust_main->address1,0,30);
+    }
+    $invoice_data{srvc_addr} = &$escape_function($service_addrs);
+
+  }
+
   $invoice_data{finance_section} = '';
   if ( $conf->config('finance_pkgclass') ) {
     my $pkg_class =
@@ -2506,6 +2644,8 @@
   my $other_money_char = $other_money_chars{$format};
   $invoice_data{'dollar'} = $other_money_char;
 
+  my $dash = $conf->exists('svc_elec_features') ? '*'x20 : '-----------';
+
   my @detail_items = ();
   my @total_items = ();
   my @buf = ();
@@ -2516,6 +2656,31 @@
   $invoice_data{'buf'} = \@buf;
   $invoice_data{'sections'} = \@sections;
 
+  # for some kind of statement
+  my @bills = qsearch({
+    'table' => 'cust_bill',
+    'hashref' => { 'custnum' => $self->custnum },
+    'order_by' => 'ORDER BY _date DESC LIMIT 19',
+  });
+  @bills = reverse(@bills);
+
+  #what about multiple details?  original code seems not to care
+  my @bill_details = ();
+  push @bill_details,
+    map { $_->cust_bill_pkg_detail }
+    map { $_->cust_bill_pkg }
+    @bills;
+
+  my @pays = reverse( qsearch({ 'table' => 'cust_pay',
+                                'hashref' => { 'custnum' => $self->custnum },
+                                'order_by' => 'ORDER BY _date DESC LIMIT 19',
+                              })
+                    );
+
+  $invoice_data{'total_bills'} = \@bills;
+  $invoice_data{'total_payments'} = \@pays;
+  $invoice_data{'total_details'} = \@bill_details;
+
   my $previous_section = { 'description' => 'Previous Charges',
                            'subtotal'    => $other_money_char.
                                             sprintf('%.2f', $pr_total),
@@ -2613,7 +2778,7 @@
   }
 
   if ( @pr_cust_bill && !$conf->exists('disable_previous_balance') ) {
-    push @buf, ['','-----------'];
+    push @buf, ['', $dash];
     push @buf, [ 'Total Previous Balance',
                  $money_char. sprintf("%10.2f", $pr_total) ];
     push @buf, ['',''];
@@ -2668,6 +2833,18 @@
       $detail->{'description'} = &$escape_function($line_item->{'description'});
       if ( exists $line_item->{'ext_description'} ) {
         @{$detail->{'ext_description'}} = @{$line_item->{'ext_description'}};
+
+        if ($conf->exists('svc_elec_features')) {
+          if ( grep { /DISCOUNT2/i } @{$line_item->{'ext_description'}} ) {
+            $invoice_data{'discount2_total'} = $line_item->{'amount'};
+            $invoice_data{'discount2_pkgnum'} = $detail->{'ref'};
+
+            #want the bare description
+            $invoice_data{'discount2_description'} = &$escape_function($_->desc)
+              foreach $self->cust_bill_pkg_pkgnum($detail->{'ref'});
+          }
+        }
+
       }
       $detail->{'amount'} = ( $old_latex ? '' : $money_char ).
                               $line_item->{'amount'};
@@ -2683,8 +2860,9 @@
                  );
     }
 
+
     if ( $section->{'description'} ) {
-      push @buf, ( ['','-----------'],
+      push @buf, ( ['', $dash],
                    [ $section->{'description'}. ' sub-total',
                       $money_char. sprintf("%10.2f", $section->{'subtotal'})
                    ],
@@ -2757,7 +2935,7 @@
   }
   $invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal);
 
-  push @buf,['','-----------'];
+  push @buf,['', $dash];
   push @buf,[( $conf->exists('disable_previous_balance') 
                ? 'Total Charges'
                : 'Total New Charges'
@@ -2791,7 +2969,7 @@
     }else{
       push @total_items, $total;
     }
-    push @buf,['','-----------'];
+    push @buf,['', $dash];
     push @buf,[$item,
                $money_char.
                sprintf( '%10.2f', $amount )
@@ -2886,7 +3064,7 @@
       }else{
         push @total_items, $total;
       }
-      push @buf,['','-----------'];
+      push @buf,['', $dash];
       push @buf,[$self->balance_due_msg, $money_char. 
         sprintf("%10.2f", $balance_due ) ];
     }

--- NEW FILE: svc_elec.pm ---
package FS::svc_elec;

use strict;
use vars qw( @ISA );
#use FS::Record qw( qsearch qsearchs );
use FS::svc_Common;

#@ISA = qw(FS::Record);
@ISA = qw( FS::svc_Common );

=head1 NAME

FS::svc_elec - Object methods for svc_elec records

=head1 SYNOPSIS

  use FS::svc_elec;

  $record = new FS::svc_elec \%hash;
  $record = new FS::svc_elec { 'column' => 'value' };

  $error = $record->insert;

  $error = $new_record->replace($old_record);

  $error = $record->delete;

  $error = $record->check;

  $error = $record->suspend;
  $error = $record->unsuspend;
  $error = $record->cancel;

=head1 DESCRIPTION

An FS::svc_elec object represents an example.  FS::svc_elec inherits from
FS::Record.  The following fields are currently supported:

=over 4

=item id - 

=item esiid - 

=item svcnum - primary key

=item countrycode - 

=item phonenum - 

=item pin - 


=back

=head1 METHODS

=over 4

=item new HASHREF

Creates a new example.  To add the example 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 { 'svc_elec'; }

=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 example.  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('svcnum')
    || $self->ut_number('id')
    || $self->ut_number('esiid')
    || $self->ut_text('countrycode')
    || $self->ut_text('phonenum')
    || $self->ut_textn('pin')
  ;
  return $error if $error;

  $self->SUPER::check;
}

=back

=head1 BUGS

The author forgot to customize this manpage.

=head1 SEE ALSO

L<FS::Record>, schema.html from the base documentation.

=cut

1;

--- NEW FILE: usage_elec_transaction867.pm ---
package FS::usage_elec_transaction867;

use strict;
use vars qw( @ISA );
use FS::Record qw( qsearch qsearchs );

@ISA = qw(FS::Record);

=head1 NAME

FS::usage_elec_transaction867 - Object methods for usage_elec_transaction867 records

=head1 SYNOPSIS

  use FS::usage_elec_transaction867;

  $record = new FS::usage_elec_transaction867 \%hash;
  $record = new FS::usage_elec_transaction867 { 'column' => 'value' };

  $error = $record->insert;

  $error = $new_record->replace($old_record);

  $error = $record->delete;

  $error = $record->check;

=head1 DESCRIPTION

An FS::usage_elec_transaction867 object represents an example.  FS::usage_elec_transaction867 inherits from
FS::Record.  The following fields are currently supported:

=over 4

=item id - primary key

=item usage_elec_id - 

=item note - 


=back

=head1 METHODS

=over 4

=item new HASHREF

Creates a new example.  To add the example 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 { 'usage_elec_transaction867'; }

=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 example.  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('id')
    || $self->ut_number('usage_elec_id')
    || $self->ut_textn('note')
  ;
  return $error if $error;

  $self->SUPER::check;
}

=back

=head1 BUGS

The author forgot to customize this manpage.

=head1 SEE ALSO

L<FS::Record>, schema.html from the base documentation.

=cut

1;


--- NEW FILE: transaction810.pm ---
package FS::transaction810;

use strict;
use vars qw( @ISA @EXPORT_OK );
use vars qw( @ISA );
use FS::Record qw( qsearch qsearchs );
use FS::UID qw( getotaker dbh );
use Exporter;
use Data::Dumper;
@ISA = qw(FS::Record);
@EXPORT_OK=qw(batch_810data_import);
=head1 NAME

FS::transaction810 - Object methods for transaction810 records

=head1 SYNOPSIS

  use FS::transaction810;

  $record = new FS::transaction810 \%hash;
  $record = new FS::transaction810 { 'column' => 'value' };

  $error = $record->insert;

  $error = $new_record->replace($old_record);

  $error = $record->delete;

  $error = $record->check;

=head1 DESCRIPTION

An FS::transaction810 object represents an example.  FS::transaction810 inherits from
FS::Record.  The following fields are currently supported:

=over 4

=item id - primary key

=item duns - 

=item inv_num - 

=item usage_867 - 

=item esiid - 

=item tdsp - 

=item due_date - 

=item inv_date - 

=item usage_kwatts - 

=item srvc_from_date - 

=item srvc_to_date - 

=item puct_fund - 

=item billed_demand - 

=item measured_demand - 

=item bill_status - 

=item type_of_bill - 

=item ack_997 - 


=back

=head1 METHODS

=over 4

=item new HASHREF

Creates a new example.  To add the example 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 { 'transaction810'; }

=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 example.  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('id')
    || $self->ut_number('tdsp_duns')
    || $self->ut_number('inv_num')
    || $self->ut_textn('ref_identification')
    || $self->ut_number('esiid')
    || $self->ut_number('tdsp')
    || $self->ut_number('due_date')
    || $self->ut_number('inv_date')
    || $self->ut_float('usage_kwatts')
    || $self->ut_number('srvc_from_date')
    || $self->ut_number('srvc_to_date')
    || $self->ut_number('puct_fund')
    || $self->ut_float('billed_demand')
    || $self->ut_floatn('measured_demand')
    || $self->ut_text('bill_status')
    || $self->ut_numbern('type_of_bill')
    || $self->ut_numbern('ack_997')
    || $self->ut_numbern('processed')
  ;
  return $error if $error;

  $self->SUPER::check;
}

sub testing {
  my $param = shift;

  my @usages = qsearch ( 'usage_elec' );

  foreach my $usage (@usages) {
    print "meter number: " . $usage->meter_number . "\n";
  }

  return "Successful sub read\n";
}

=item batch_810data_import


Importing a CVS file with the following column:
  duns inv_num 867_usage esiid tdsp due_date inv_date usage_kwatts 
  srvc_from_date srvc_to_date puct_fund billed_demand 
  measured_demand bill_status type_of_bill 997_ack

=cut

#@EXPORT_OK=qw(batch_810data_import);
sub batch_810data_import {
  #my $param = shift;
  my ($fh,$format) = @_;

#  print "\n\n****************** the cvs file\n\n";
#  print (<$fh>);
#  print ("\n$format\n");
#  return "done\n";

  #my $fh = $param->{filehandle};
  #my $format = $param->{'format'};
  my $error;
  my $debug = 0;
  
  my @fields;
  if ( $format eq 'extended' ) {
    @fields = qw( 
		  tdsp_duns inv_num ref_identification esiid tdsp due_date
		  inv_date usage_kwatts srvc_from_date srvc_to_date
		  puct_fund billed_demand measured_demand bill_status
		  type_of_bill
                );
  } else {
    die "unknown format $format";
  }

  eval "use Text::CSV_XS;";
  die $@ if $@;

  my $csv = new Text::CSV_XS;
  #warn $csv;
  #warn $fh;

  my $imported = 0;
  #my $columns;

  local $SIG{HUP} = 'IGNORE';
  local $SIG{INT} = 'IGNORE';
  local $SIG{QUIT} = 'IGNORE';
  local $SIG{TERM} = 'IGNORE';
  local $SIG{TSTP} = 'IGNORE';
  local $SIG{PIPE} = 'IGNORE';

  my $oldAutoCommit = $FS::UID::AutoCommit;
  local $FS::UID::AutoCommit = 0;
  my $dbh = dbh;
  
  #while ( $columns = $csv->getline($fh) ) {
  my $line;
  while ( defined($line=<$fh>) ) {

    $csv->parse($line) or do {
      $dbh->rollback if $oldAutoCommit;
      return "can't parse: ". $csv->error_input();
    };

    my @columns = $csv->fields();
    #warn join('-', at columns);

    # this hash will hold each CVS line
    my %transaction810_data;

    my $billtime = time;
#    my %cust_pkg = ( pkgpart => $pkgpart );
    my %svc_acct = ();
    foreach my $field ( @fields ) {
	# -cal  this section is ignored by the 810 import 
	$transaction810_data{$field} = shift @columns;
    }

    # initialize the column 'ack_997' to 0 (not yet sent ack)
    $transaction810_data{'ack_997'} = 0;

    # initialize the column 'processed' to 0 (not process yet)
    $transaction810_data{'processed'} = 0;

   print Dumper(\%transaction810_data) if $debug;
   #return ("done\n");

    ### check to see if the invoice is already in transaction810 table
    # if so then print a warning
    my $inv_num = $transaction810_data{'inv_num'};
    my $search_res = qsearchs ( 'transaction810',
                                {'inv_num' => $inv_num}
                              );
    if ( $search_res ) {
      ###
      #place some code to fix this problem here
      #
      print "$line\n";
      print "OOps! a transaction with invoice number "
           ."$transaction810_data{'inv_num'}\n"
           ."\t is in the table already!!\n";
    }
    else {
      my $transaction810_obj = new FS::transaction810( \%transaction810_data );
      $error = $transaction810_obj->insert;
      if ( $error ) {
        $dbh->rollback if $oldAutoCommit;
        return "can't bill customer for $line: $error";
      }
      $imported++;
    }
  }

  $dbh->commit or die $dbh->errstr if $oldAutoCommit;

  return "Empty file!" unless $imported;

  ''; #no error

}


=back

=head1 BUGS

The author forgot to customize this manpage.

=head1 SEE ALSO

L<FS::Record>, schema.html from the base documentation.

=cut

1;


Index: part_pkg.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/part_pkg.pm,v
retrieving revision 1.105
retrieving revision 1.105.2.1
diff -u -w -d -r1.105 -r1.105.2.1
--- part_pkg.pm	29 Jul 2010 23:11:31 -0000	1.105
+++ part_pkg.pm	2 Aug 2010 19:49:19 -0000	1.105.2.1
@@ -1418,6 +1418,7 @@
       warn "no %info hash found in FS::part_pkg::$mod, skipping\n";
       next;
     }
+
     warn "got plan info from FS::part_pkg::$mod: $info\n" if $DEBUG;
     if ( exists($info->{'disabled'}) && $info->{'disabled'} ) {
       warn "skipping disabled plan FS::part_pkg::$mod" if $DEBUG;
@@ -1433,7 +1434,15 @@
   keys %info;
 
 sub plan_info {
-  \%plans;
+  my $conf = new FS::Conf;
+  return \%plans unless $conf->exists('svc_elec_features');
+
+  tie my %result, 'Tie::IxHash',
+    map { $_ => $plans{$_} }
+    grep { $plans{$_}{svc_elec_compatible} }
+    keys %plans;
+
+  \%result;
 }
 
 

--- NEW FILE: transaction867.pm ---
package FS::transaction867;

use strict;
use vars qw( @ISA @EXPORT_OK );
use FS::Record qw( qsearch qsearchs );
use FS::UID qw( getotaker dbh );
use Exporter;
use Data::Dumper;

@ISA = qw(FS::Record);
@EXPORT_OK = qw(batch_867data_import);

=head1 NAME

FS::transaction867 - Object methods for transaction867 records

=head1 SYNOPSIS

  use FS::transaction867;

  $record = new FS::transaction867 \%hash;
  $record = new FS::transaction867 { 'column' => 'value' };

  $error = $record->insert;

  $error = $new_record->replace($old_record);

  $error = $record->delete;

  $error = $record->check;

=head1 DESCRIPTION

An FS::transaction867 object represents an example.  FS::transaction867 inherits from
FS::Record.  The following fields are currently supported:

=over 4

=item id - primary key

=item tdsp_duns - 

=item ref_identification - 

=item esiid - 

=item trans_creation_date - 

=item meter_no - 

=item srvc_period_start_date - 

=item srvc_period_end_date - 

=item prev_read_kwatts - 

=item curr_read_kwatts - 

=item meter_multiplier - 

=item usage_kwatts - 

=item measured_demand - 

=item ack_997 - 

=item processed - 


=back

=head1 METHODS

=over 4

=item new HASHREF

Creates a new example.  To add the example 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 { 'transaction867'; }

=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 example.  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('id')
    || $self->ut_number('tdsp_duns')
    || $self->ut_text('ref_identification')
    || $self->ut_text('esiid')
    || $self->ut_number('trans_creation_date')
    || $self->ut_text('meter_no')
    || $self->ut_number('srvc_period_start_date')
    || $self->ut_number('srvc_period_end_date')
    || $self->ut_float('prev_read_kwatts')
    || $self->ut_float('curr_read_kwatts')
    || $self->ut_float('meter_multiplier')
    || $self->ut_float('usage_kwatts')
    || $self->ut_floatn('measured_demand')
    || $self->ut_number('ack_997')
    || $self->ut_number('processed')
  ;
  return $error if $error;

  $self->SUPER::check;
}




=item batch_867data_import


Importing a CVS file with the following column:
  867_usage esiid date meter srvc_from_date srvc_to_date previous_read_kwatts 
  current_read_kwatts mult usage_kwatts measure_demand 997_ack

=cut

#@EXPORT_OK=qw(batch_867data_import);
sub batch_867data_import {
  #my $param = shift;
  my ($fh,$format) = @_;

#  print "\n\n****************** the cvs file\n\n";
#  print (<$fh>);
#  print ("\n$format\n");
#  return "done\n";

  #my $fh = $param->{filehandle};
  #my $format = $param->{'format'};
  my $error;
  my $debug = 0;
  
  my @fields;
  if ( $format eq 'extended' ) {
    @fields = qw( 
		  tdsp_duns ref_identification esiid trans_creation_date
                  meter_no srvc_period_start_date srvc_period_end_date
                  prev_read_kwatts curr_read_kwatts meter_multiplier
                  usage_kwatts measured_demand ack_997 processed
                );
  } else {
    die "unknown format $format";
  }

  eval "use Text::CSV_XS;";
  die $@ if $@;

  my $csv = new Text::CSV_XS;
  #warn $csv;
  #warn $fh;

  my $imported = 0;
  #my $columns;

  local $SIG{HUP} = 'IGNORE';
  local $SIG{INT} = 'IGNORE';
  local $SIG{QUIT} = 'IGNORE';
  local $SIG{TERM} = 'IGNORE';
  local $SIG{TSTP} = 'IGNORE';
  local $SIG{PIPE} = 'IGNORE';

  my $oldAutoCommit = $FS::UID::AutoCommit;
  local $FS::UID::AutoCommit = 0;
  my $dbh = dbh;
  
  #while ( $columns = $csv->getline($fh) ) {
  my $line;
  while ( defined($line=<$fh>) ) {

    $csv->parse($line) or do {
      $dbh->rollback if $oldAutoCommit;
      return "can't parse: ". $csv->error_input();
    };

    my @columns = $csv->fields();
    #warn join('-', at columns);

    # this hash will hold each CVS line
    my %transaction867_data;

    my $billtime = time;
#    my %cust_pkg = ( pkgpart => $pkgpart );
    my %svc_acct = ();
    foreach my $field ( @fields ) {
	# -cal  this section is ignored by the 867 import 
	$transaction867_data{$field} = shift @columns;
    }

    # make sure to set the 'ack_997' column
    $transaction867_data{'ack_997'} = 0;

    # make sure to set the 'processed' column
    $transaction867_data{'processed'} = 0;

    print Dumper(\%transaction867_data) if $debug;

    ### Check to see if the invoice is already in transaction810 table
    # if so then print a warning

    my $ref_identification = $transaction867_data{'ref_identification'};
    my $search_res = qsearchs ( 'transaction867',
                                {'ref_identification' => $ref_identification}
                              );

    if ($search_res) {

      ### 
      # place some code here to fix the problme of trying to insert
      # data that have the same references identification number

      print "$line\n";
      print "OOps! a transaction with the same references identification"
           ." number $ref_identification\n"
           ."\tis in the transaction867 table already!!\n";

    }
    else {

      my $transaction867_obj = new FS::transaction867( \%transaction867_data );
      $error = $transaction867_obj->insert;
      if ( $error ) {
        $dbh->rollback if $oldAutoCommit;
        return "can't bill customer for $line: $error";
      }

      $imported++;

    }

  } #end while

  $dbh->commit or die $dbh->errstr if $oldAutoCommit;

  return "Empty file!" unless $imported;

  ''; #no error

}



=back

=head1 BUGS

The author forgot to customize this manpage.

=head1 SEE ALSO

L<FS::Record>, schema.html from the base documentation.

=cut

1;


Index: cust_main.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_main.pm,v
retrieving revision 1.527
retrieving revision 1.527.2.1
diff -u -w -d -r1.527 -r1.527.2.1
--- cust_main.pm	28 Jul 2010 23:16:29 -0000	1.527
+++ cust_main.pm	2 Aug 2010 19:49:18 -0000	1.527.2.1
@@ -22,6 +22,7 @@
 use Tie::IxHash;
 use Digest::MD5 qw(md5_base64);
 use Date::Format;
+use Date::Parse;
 #use Date::Manip;
 use File::Temp qw( tempfile );
 use String::Approx qw(amatch);
@@ -71,6 +72,10 @@
 use FS::payment_gateway;
 use FS::agent_payment_gateway;
 use FS::banned_pay;
+use FS::transaction810;
+use FS::transaction867;
+use FS::usage_elec;
+use FS::usage_elec_transaction867;
 use FS::TicketSystem;
 
 @EXPORT_OK = qw( smart_search );
@@ -2204,6 +2209,9 @@
 # This should be generalized to use config options to determine order.
 sub sort_packages {
   
+  return $a->pkgnum <=> $b->pkgnum
+    if $conf->exists('svc_elec_features');
+
   my $locationsort = ( $a->locationnum || 0 ) <=> ( $b->locationnum || 0 );
   return $locationsort if $locationsort;
 
@@ -2873,6 +2881,9 @@
 
   my %cust_bill_pkg = map { $_ => [] } @passes;
 
+  # some values we may need for elec billing
+  my $elec_hash = { 'pkgnum' => 0, 'rate' => 0, 'detail' => ' ' };
+  
   ###
   # find the packages which are due for billing, find out how much they are
   # & generate invoice database.
@@ -2881,14 +2892,22 @@
   my %total_setup   = map { my $z = 0; $_ => \$z; } @passes;
   my %total_recur   = map { my $z = 0; $_ => \$z; } @passes;
 
+  ###
+  # XXX this looks to be redundant in that we have cust_pkg_discount
+  # and cust_bill_pkg_discount which should be able to factor this out
+  ###
+  my %total_discount = map { my $z = 0; $_ => \$z; } @passes;
+
   my %taxlisthash = map { $_ => {} } @passes;
 
   my @precommit_hooks = ();
 
   $options{'pkg_list'} ||= [ $self->ncancelled_pkgs ];  #param checks?
+  $options{'elec_hash'} = $elec_hash;
   foreach my $cust_pkg ( @{ $options{'pkg_list'} } ) {
 
     next if $options{'not_pkgpart'}->{$cust_pkg->pkgpart};
+    next if $conf->exists('svc_elec_features') && $cust_pkg->susp; # eh?
 
     warn "  bill package ". $cust_pkg->pkgnum. "\n" if $DEBUG > 1;
 
@@ -2914,6 +2933,7 @@
                             'line_items'          => $cust_bill_pkg{$pass},
                             'setup'               => $total_setup{$pass},
                             'recur'               => $total_recur{$pass},
+                            'discount'            => $total_discount{$pass},
                             'tax_matrix'          => $taxlisthash{$pass},
                             'time'                => $time,
                             'real_pkgpart'        => $real_pkgpart,
@@ -2928,6 +2948,63 @@
 
   } #foreach my $cust_pkg
 
+
+  my $average_price = 0;
+  my $total_recur = 0; $total_recur += ${ $total_recur{$_} } foreach @passes;
+  $average_price = $total_recur/$elec_hash->{energy_usage}
+    if $elec_hash->{energy_usage};
+
+  my $round4 = sub { int( shift() * 10000 + .5 ) / 10000 }; #very weird
+  $elec_hash->{average_price} = &$round4($average_price);
+  $elec_hash->{balance} = $self->balance;
+  $elec_hash->{last_pay} = 0;
+  $elec_hash->{last_pay_date} = 0;
+
+  # XXX this is a roundabout way to get this info on the invoice
+  my $last_payment = qsearchs({
+                                'table'     => 'cust_pay',
+                                'hashref'   => { 'op' => '=',
+                                                 'custnum' => $self->custnum,
+                                               },
+                                'extra_sql' => 'ORDER BY _date DESC',
+                             });
+
+  if (defined($last_payment)) {
+    $elec_hash->{last_pay} = $last_payment->paid;
+    $elec_hash->{last_pay_date} = $last_payment->date;
+  }
+
+  # XXX whoa
+  my $returnaddress;
+  if ( length($conf->config_orbase('invoice_latexreturnaddress')) ) {
+    $returnaddress = join("\n", $conf->config_orbase('invoice_latexreturnaddress'));
+  } else {
+    $returnaddress = '~';
+  }
+  $elec_hash->{returnaddress} = $returnaddress;
+
+  # XXX again? need to get rid of this loop
+  if ($conf->exists('svc_elec_features')) {
+    foreach my $cust_pkg ($self->ncancelled_pkgs) {
+      next if $cust_pkg->susp;
+      next unless $cust_pkg->bill;
+
+      my @svc_external = grep { $_->title =~ /esiid/i }
+                         grep { ref($_) eq 'FS::svc_external' }
+                         map { $_->svc_x }
+                         $cust_pkg->cust_svc;
+      next unless @svc_external;
+      my $rate = $svc_external[0]->cust_svc->cust_pkg->part_pkg->option('rate');
+      $elec_hash->{rate} = $rate unless $elec_hash->{rate};
+      $elec_hash->{pkg_info} =
+        $svc_external[0]->cust_svc->cust_pkg->part_pkg->pkg;
+      $elec_hash->{esiid} = $svc_external[0]->id;
+      my $transaction867 =
+        qsearchs('transaction867', {'esiid' => $elec_hash->{esiid} });
+      $elec_hash->{meter_number} = $transaction867->meter_no if $transaction867;
+    }
+  }
+  
   #if the customer isn't on an automatic payby, everything can go on a single
   #invoice anyway?
   #if ( $cust_main->payby !~ /^(CARD|CHEK)$/ ) {
@@ -2965,6 +3042,7 @@
                                 'line_items'          => \@cust_bill_pkg,
                                 'setup'               => $total_setup{$pass},
                                 'recur'               => $total_recur{$pass},
+                                'discount'            => $total_discount{$pass},
                                 'tax_matrix'          => $taxlisthash{$pass},
                                 'time'                => $time,
                                 'real_pkgpart'        => $real_pkgpart,
@@ -2988,11 +3066,15 @@
       return $listref_or_error;
     }
 
+    my $total_tax = 0; # per pass?
     foreach my $taxline ( @$listref_or_error ) {
       ${ $total_setup{$pass} } =
         sprintf('%.2f', ${ $total_setup{$pass} } + $taxline->setup );
+      $total_tax = sprintf('%.2f', $total_tax + $taxline->setup );
       push @cust_bill_pkg, $taxline;
     }
+    $elec_hash->{taxes} = $total_tax;
+    
 
     #add tax adjustments
     warn "adding tax adjustments...\n" if $DEBUG > 2;
@@ -3022,7 +3104,7 @@
 
     }
 
-    my $charged = sprintf('%.2f', ${ $total_setup{$pass} } + ${ $total_recur{$pass} } );
+    my $charged = sprintf('%.2f', ${ $total_setup{$pass} } + ${ $total_recur{$pass} } - ${ $total_discount{$pass} });
 
     my @cust_bill = $self->cust_bill;
     my $balance = $self->balance;
@@ -3050,6 +3132,16 @@
       return "can't create invoice for customer #". $self->custnum. ": $error";
     }
 
+    if ( $conf->exists('svc_elec_features') ) {
+      my $pkgnum = delete($elec_hash->{pkgnum});
+      my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail $elec_hash;
+      foreach my $cust_bill_pkg( @cust_bill_pkg ) {
+        next unless $cust_bill_pkg->pkgnum == $pkgnum;
+        push @{$cust_bill_pkg->get('details')}, $cust_bill_pkg_detail;
+        last;
+      }
+    }
+
     foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
       $cust_bill_pkg->invnum($cust_bill->invnum); 
       my $error = $cust_bill_pkg->insert;
@@ -3262,6 +3354,7 @@
   my $cust_bill_pkgs = $params{line_items} or die "no line buffer specified";
   my $total_setup = $params{setup} or die "no setup accumulator specified";
   my $total_recur = $params{recur} or die "no recur accumulator specified";
+  my $total_discount = $params{discount} or die "no discount accumulator specified";
   my $taxlisthash = $params{tax_matrix} or die "no tax accumulator specified";
   my $time = $params{'time'} or die "no time specified";
   my (%options) = %{$params{options}};
@@ -3270,6 +3363,7 @@
   my $real_pkgpart = $params{real_pkgpart};
   my %hash = $cust_pkg->hash;
   my $old_cust_pkg = new FS::cust_pkg \%hash;
+  my $elec_hash = $options{elec_hash};
 
   my @details = ();
   my @discounts = ();
@@ -3314,6 +3408,25 @@
     $cust_pkg->setfield('start_date', '')
       if $cust_pkg->start_date;
 
+    if ( $setup != 0 ) {
+      my $value = $elec_hash->{one_time_charge} || 0;
+      my $string = $elec_hash->{one_time_description} || '';
+      $string = '/'. $string if $string;
+      $elec_hash->{one_time_charge} = sprintf('%.2f', $setup+$value);
+      ### or should we be using $part_pkg below?
+      $elec_hash->{one_time_description} = $cust_pkg->part_pkg->pkg. $string;
+    } else {
+      $elec_hash->{setup_fee} = $setup;
+    }
+  }
+
+  my $is_energypkg;
+  if ( $conf->exists('svc_elec_features') ) {
+    $is_energypkg = 1
+      if grep { $_->title =~ /esiid/i }
+         grep { ref($_) eq 'FS::svc_external' }
+         map { $_->svc_x }
+         $cust_pkg->cust_svc;
   }
 
   ###
@@ -3324,10 +3437,13 @@
   my $recur = 0;
   my $unitrecur = 0;
   my $sdate;
+  my $testdate = $conf->exists('svc_elec_features') 
+                 ? ( $cust_pkg->getfield('last_bill') || 0 )
+                 : ( $cust_pkg->getfield('bill') || 0 );
   if (     ! $cust_pkg->get('susp')
        and ! $cust_pkg->get('start_date')
        and ( $part_pkg->getfield('freq') ne '0'
-             && ( $cust_pkg->getfield('bill') || 0 ) <= $time
+             && $testdate <= $time
            )
         || ( $part_pkg->plan eq 'voip_cdr'
               && $part_pkg->option('bill_every_call')
@@ -3365,9 +3481,53 @@
     return "$@ running $method for $cust_pkg\n"
       if ( $@ );
 
+    if ($recur != 0) {
+      my $lastbilldate = $cust_pkg->last_bill || 0;
+
+      if ($is_energypkg) {
+        my $vrate = $cust_pkg->part_pkg->option('vrate', 'quiet');
+        my $rate = $cust_pkg->part_pkg->option('rate');
+        my %var_rate;
+        if ($vrate) {
+          foreach my $rate_frame ( split(';',$vrate) ) {
+            my ($period, $period_rate) = split(':', $rate_frame);
+            my ($yr,$mo) = split('-',$period);
+            $var_rate{$yr}{$mo} = $period_rate;
+          }
+          my @cust_svc = grep { $_->title =~ /esiid/ }
+             map { $_->svc_x }
+             $cust_pkg->cust_svc('svc_external');
+          my $cust_svc = $cust_svc[0] if @cust_svc;
+          # XXX ok: these lines are clearly bunk as they return the empty list
+          my @usage_elecs =
+            qsearch( 'usage_elec',
+                     { 'svcnum' => $cust_svc->svcnum,
+                       '_date'  => { op => '>', 'value' => $lastbilldate },
+                       'extra_sql' => 'ORDER BY _date_',
+                     }
+                   );
+          if(defined($usage_elecs[0])) {
+            my $usage_enddate_year =
+              time2str('%Y', $usage_elecs[0]->curr_date);
+            my $usage_enddate_month =
+              time2str('%m', $usage_elecs[0]->curr_date);
+            $rate = $var_rate{$usage_enddate_year}{$usage_enddate_month}
+              if exists($var_rate{$usage_enddate_year}{$usage_enddate_month});
+          }
+        }
+
+        $elec_hash->{rate} = $rate;
+         
+        $elec_hash->{discount1_rate} =$part_pkg->option('rate1_discount');
+      }
+    }
+
     if ( $increment_next_bill ) {
 
-      my $next_bill = $part_pkg->add_freq($sdate);
+      # this is probably better handled differently than svc_elect_feature
+      # is this a recur_temporality issue? 
+      my $next_bill =
+        $part_pkg->add_freq($conf->exists('svc_elec_feature') ? $time : $sdate);
       return "unparsable frequency: ". $part_pkg->freq
         if $next_bill == -1;
   
@@ -3408,6 +3568,9 @@
         if $error; #just in case
     }
   
+    $cust_pkg->last_bill($time)
+      if $conf->exists('svc_elec_features') && $part_pkg->option('rate');
+
     $setup = sprintf( "%.2f", $setup );
     $recur = sprintf( "%.2f", $recur );
     if ( $setup < 0 && ! $conf->exists('allow_negative_charges') ) {
@@ -3451,6 +3614,29 @@
         #$cust_bill_pkg->edate( $time ) if $options{cancel};
       }
 
+      if ($conf->exists('svc_elec_features')) {
+        if( $recur != 0 ){
+          my  $cust_svc=qsearchs('cust_svc',{'pkgnum' => $cust_pkg->pkgnum});
+
+          if ($is_energypkg) {
+            my $lastbilldate = $cust_pkg->last_bill || 0;
+            my  $usage_elec = qsearchs ({
+              'table'     => 'usage_elec',
+              'hashref'   => { 'svcnum'    => $cust_svc->svcnum,
+                               '_date'     => { 'op' => '>',
+                                                'value' => $lastbilldate,
+                                              },
+                             },
+              'extra_sql' => 'ORDER BY _date'
+            });
+            if ($usage_elec) {
+              $cust_bill_pkg->sdate($usage_elec->prev_date);
+              $cust_bill_pkg->edate($usage_elec->curr_date);
+            }
+          }
+        }
+      }
+
       $cust_bill_pkg->pkgpart_override($part_pkg->pkgpart)
         unless $part_pkg->pkgpart == $real_pkgpart;
 
@@ -3471,6 +3657,99 @@
       
   } #if $line_items
 
+  # logically here?  might need to be before taxes?
+  if ($is_energypkg) {
+    if($recur != 0 and $cust_pkg->pkgnum) {
+      my $vrate = $cust_pkg->part_pkg->option('vrate', 'quiet');
+      my $pkg_rate = $cust_pkg->part_pkg->option('rate');
+      my %var_rate;
+
+      # a bit of extra bunk
+      my  $cust_svc=qsearchs('cust_svc',{'pkgnum' => $cust_pkg->pkgnum});
+      my $lastbilldate = $cust_pkg->last_bill || 0;
+
+      if ($vrate) {
+        foreach my $rate_frame ( split(';',$vrate) ) {
+          my ($period, $period_rate) = split(':', $rate_frame);
+          my ($yr,$mo) = split('-',$period);
+          $var_rate{$yr}{$mo} = $period_rate;
+        }
+        # XXX ok: these lines are clearly bunk as they return the empty list
+        my @usage_elecs =
+          qsearch( 'usage_elec',
+                   { 'svcnum' => $cust_svc->svcnum,
+                     '_date'  => { op => '>', 'value' => $lastbilldate },
+                     'extra_sql' => 'ORDER BY _date_',
+                   }
+                 );
+        if(defined($usage_elecs[0])) {
+          my $usage_enddate_year =
+            time2str('%Y', $usage_elecs[0]->curr_date);
+          my $usage_enddate_month =
+            time2str('%m', $usage_elecs[0]->curr_date);
+          $pkg_rate = $var_rate{$usage_enddate_year}{$usage_enddate_month}
+            if exists($var_rate{$usage_enddate_year}{$usage_enddate_month});
+        }
+      }
+      $elec_hash->{rate} = $pkg_rate unless $elec_hash->{rate};
+      my $late_fee = $part_pkg->option('penalty') || 0;
+      my $pkg_gr_fee = $part_pkg->option('gr_fee') || 0;
+      my $pkg_basic_fee = $part_pkg->option('base_fee') || 0;
+      $elec_hash->{'pkgnum'} = $cust_pkg->pkgnum;
+      my $usage_elec = qsearchs({
+        'table' => 'usage_elec',
+        'hashref' => { 'svcnum' => $cust_svc->svcnum,
+                       '_date'  => { 'op' => '>', 'value' => $lastbilldate },
+                     },
+        'extra_sql' => 'ORDER BY _date',
+      });
+      if ($usage_elec) {
+        my $usage_elec_transaction867 =
+          qsearchs( 'usage_elec_transaction867',
+                    {'usage_elec_id' => $usage_elec->id}
+                  );
+        $elec_hash->{note} = $usage_elec_transaction867->note
+          if $usage_elec_transaction867;
+
+        my $usagefromelec = $usage_elec->getUsage;
+
+        my $round = sub { sprintf('%.2f', int( shift() * 100 + .5 ) / 100) };
+        if ( $elec_hash->{discount1_rate} ) {
+          $elec_hash->{discount1_total} =
+            &$round($elec_hash->{discount1_rate} * $usagefromelec);
+          $$total_discount += $elec_hash->{discount1_total};
+        }
+
+        my $charge = &$round($usagefromelec * $pkg_rate);
+        $elec_hash->{meter_number} = $usage_elec->meter_number;
+        $elec_hash->{energy_base} = sprintf('%.2f', $pkg_basic_fee);
+        $elec_hash->{energy_charge} =
+          sprintf('%.2f', $usagefromelec * $elec_hash->{rate});
+        $elec_hash->{tdsp} = $usage_elec->tdsp;
+        $elec_hash->{number_of_days} = $usage_elec->getNumberOfDays;
+        $elec_hash->{energy_usage} = $usagefromelec;
+        $elec_hash->{demanded_bill} = $usage_elec->billed_demand;
+        $elec_hash->{measured_bill} = $usage_elec->measured_demand;
+        $elec_hash->{meter_multiplier} = $usage_elec->meter_multiplier;
+
+        $elec_hash->{balance} = 0;
+        my $thistdsp = $usage_elec->tdsp;
+        $elec_hash->{gr_fee} =
+          sprintf('%.2f', ($charge+$thistdsp+$pkg_basic_fee) * $pkg_gr_fee);
+        $elec_hash->{last_pay} = 0;
+        $elec_hash->{last_pay_date} = 0;
+        $elec_hash->{taxes} = 0;
+        $elec_hash->{prev_date} = $usage_elec->prev_date;
+        $elec_hash->{curr_date} = $usage_elec->curr_date;
+        $elec_hash->{prev_read} = $usage_elec->prev_read;
+        $elec_hash->{curr_read} = $usage_elec->curr_read;
+        $late_fee = $late_fee * # a rate i guess
+          sprintf('%.2f', ($charge+$thistdsp+$pkg_basic_fee+$elec_hash->{gr_fee}));
+        $elec_hash->{late_fee} = $late_fee;
+      }
+    }
+  }
+
   '';
 
 }
@@ -8740,7 +9019,12 @@
     }
 
     if ( $row{'amount'} > 0 ) {
-      my $error = $cust_main->charge($row{'amount'}, $row{'pkg'});
+      my @args = ();
+      if (exists($row{taxclass})){
+        push @args, sprintf("\$%.2f", $row{amount});
+        push @args, $row{taxclass};
+      }
+      my $error = $cust_main->charge($row{'amount'}, $row{'pkg'}, @args);
       if ( $error ) {
         $dbh->rollback if $oldAutoCommit;
         return $error;
@@ -9098,6 +9382,490 @@
   $cust_main->bill_and_collect( %$param );
 }
 
+# This import script was written to import old OnPAC customer from the 
+# previous billing database into freeside
+# coder: Cal Tran
+# dob: 3/20/06
+
+=item batch_import_onp
+
+=cut
+
+sub batch_import_onp {
+  my $param = shift;
+  #warn join('-',keys %$param);
+  my $fh = $param->{filehandle};
+  my $agentnum = $param->{agentnum};
+
+  my $refnum = $param->{refnum};
+  my $pkgpart = $param->{pkgpart};
+
+  my $debug = 1;
+  #my @fields = @{$param->{fields}};
+  my $format = $param->{'format'};
+  my (@fields, @incoming_fields);
+  my $payby;
+  if ( $format eq 'simple' ) {
+    @fields = qw( cust_pkg.setup dayphone first last
+                  address1 address2 city state zip comments );
+    $payby = 'BILL';
+  } elsif ( $format eq 'extended' ) {
+    @fields = qw( agent_custid refnum
+                  last first address1 address2 city state zip country
+                  daytime night
+                  ship_last ship_first ship_address1 ship_address2
+                  ship_city ship_state ship_zip ship_country
+                  payinfo paycvv paydate
+                  invoicing_list
+                  cust_pkg.pkgpart
+                  svc_acct.username svc_acct._password 
+                );
+    @incoming_fields = qw( custnum
+                           name address1 address2 citystate zip
+                           ss
+                           daytime night
+                           ship_name ship_address1 ship_address2 
+                           ship_citystate ship_zip
+                           newcustdate
+                         );
+    # mapping notes of incoming_field
+    # legend: incoming_field = field         
+    # *custnum     - this is not map to any of the original field
+    # name           = last, first
+    # address1       = address1
+    # address2       = address2
+    # citystate      = city state
+    # zip            = zip          
+    # ss             - this is not map to any of the original field
+    # daytime        = daytime
+    # night          = night
+    # ship_name      = ship_last, ship_first
+    # ship_address1  = ship_address1
+    # ship_address2  = ship_address2
+    # ship_citystate = ship_city ship_state
+    # ship_zip       = ship_zip          
+    # * newcustdate - this is not map to any of the original field
+
+    $payby = 'BILL';
+
+  } else {
+    die "unknown format $format";
+  }
+
+  eval "use Text::CSV_XS;";
+  die $@ if $@;
+
+  my $csv = new Text::CSV_XS;
+  #warn $csv;
+  #warn $fh;
+
+  my $imported = 0;
+  #my $columns;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+  
+  #while ( $columns = $csv->getline($fh) ) {
+  my $line;
+  while ( defined($line=<$fh>) ) {
+
+    $csv->parse($line) or do {
+      $dbh->rollback if $oldAutoCommit;
+      return "can't parse: ". $csv->error_input();
+    };
+
+    my @columns = $csv->fields();
+    my $inpstr = $debug ? join ('|', at columns) : '';
+    #warn join('-', at columns);
+
+    my %cust_main = (
+      agentnum => $agentnum,
+      refnum   => 1,
+      country  => $conf->config('countrydefault') || 'US',
+      daytime  =>'',
+      night    =>'',
+      ship_country  => $conf->config('countrydefault') || 'US',
+      payby    => $payby, #default
+      paydate  => '12/2037', #default
+    );
+    my $billtime = time;
+    my %cust_pkg = ( pkgpart => $pkgpart );
+    my %svc_acct = ();
+
+    # getting rid of all leading and trailing spaces
+    foreach my $value (@columns) {
+      $value =~ s/^\s*(.*?)\s*$/$1/;
+    }
+
+    foreach my $field ( @incoming_fields ) {
+
+      if ($field eq 'custnum') {
+        ### verify custnum correctness
+        my $cn = $columns[0];
+        # note: If the custnum is null, then we are assuming that 
+        # the custnum is fill in from the system
+        next if !$cn;
+
+        return "error: custnum '$cn' need to be a 9 digit number.<br>$inpstr" 
+                                                         if ($cn !~ /^\d{9}$/); 
+
+        return "error: custnum '$cn' must start with a 9 or 1.<br>$inpstr" 
+                                                         if ($cn !~ /^(1|9)/); 
+ 
+        $cust_main{$field} = shift @columns;
+      }
+      elsif ( $field =~ /^(name|ship_name)$/ ) {
+        my ($last,$first) = split (/,/,$columns[0]);
+        $last =~ s/\s*$//;  # remove trailing spaces
+        $first =~ s/^\s*//; # remove leading spaces
+
+        if ($field eq 'name') {
+          $cust_main{'last'} = $last;
+          $cust_main{'first'} = $first;
+        }
+        else {
+          $cust_main{'ship_last'} = $last;
+          $cust_main{'ship_first'} = $first;
+        }
+
+        shift @columns;
+
+      }
+      elsif ($field =~ /^(citystate|ship_citystate)$/) {
+
+        #if ( $columns[0] =~ /(.*?)\s([a-zA-Z]{2})$/ ) { #use for any state
+        if ( $columns[0] =~ /(.*)\s(TX)$/i ) { # TX only
+          my ($city,$state) = (uc $1,uc $2);
+          $city =~ s/\s*$//;  # remove trailing spaces
+
+          if ($field eq 'citystate') {
+            $cust_main{'city'} = $city;
+            $cust_main{'state'} = $state;
+          }
+          else {
+            $cust_main{'ship_city'} = $city;
+            $cust_main{'ship_state'} = $state;
+          }
+        }
+        else {
+          return "error: Field city_state '".$columns[0]."',don't match"
+                ." format 'city state'. I.E. SUGAR LAND TX"
+                ."<br>$inpstr";
+        }
+
+        shift @columns;
+     
+      }
+      elsif ( $field =~ /^(zip|ship_zip)$/ ) {
+        if ( $columns[0] =~ /^(\d{5}|\d{9})$/ ) {
+          my $zipcode = $1;
+          # cludge.  Because the system is not accepting a straight
+          # 9 digit zipcode.  Need to have in format ddddd-dddd
+          $zipcode =~ s/^(\d{5})(\d{4})$/$1\-$2/;
+          $cust_main{$field} = $zipcode;
+        }
+        else {
+          return "error: Zip code '".$columns[0]."' need to be in the format "
+                ."of 5 digit or 9 digit. I.E. 75227 or 752271212"
+                ."<br>$inpstr";
+        }
+        shift @columns;
+      }
+      elsif ( $field eq 'ss' ) {
+        if ($columns[0]) { #ignore if blak
+          if ( $columns[0] =~ /^\d{9}$/ ) {
+            # verify social security number format
+            $cust_main{$field} = $columns[0];
+          }
+          else {
+            return "error: Social Security number ".$columns[0]."' need to be in" 
+                  ." the format of 9 digit. No dash or non digit character are"
+                  ." allowed.  I.E. 512342898"
+                  ."<br>$inpstr";
+          }
+        }
+        shift @columns;
+      }
+      elsif ( $field =~ /^(daytime|night)$/ ) {
+        if ( $columns[0] ) { #ignore if blank
+          if ( $columns[0] =~ /^(\d{3})(\d{3})(\d{4})$/ ) {
+            # duplicate the ph # to service address too
+            $cust_main{$field} = $cust_main{"ship_$field"}= "$1\-$2\-$3";
+          }
+          else {
+            return "error: Phone number ".$columns[0]."' need to be in the format "
+                  ."of 10 digit. No dash or non digit character are allowed."
+                  ." I.E. 8179072171"
+                  ."<br>$inpstr";
+          }
+        }
+        shift @columns;
+
+      }
+      elsif ( $field eq 'newcustdate' ) {
+
+        # string format coming in is in the form of m/d/yyyy
+        if ($columns[0] =~ /^\d{1,2}\/\d{1,2}\/\d{4}$/) {
+          my($month, $day, $year) = split(/\//,$columns[0]);
+
+          # pad month and day with a '0' if they are single digit
+          $month =~ s/^(\d)$/0$1/;
+          $day   =~ s/^(\d)$/0$1/;
+ 
+          my $date = str2time("${year}${month}${day}");
+        
+          $cust_main{'signupdate'} = $date;
+        }
+        else {
+          return "error: Time '".$columns[0]."' format is not correct."
+                ." Accepted format is m/d/yyyy i.e. 3/5/2002."
+                ."<br>$inpstr";
+        }
+        shift @columns;
+
+      }
+      else {
+        $cust_main{$field} = shift @columns; 
+      }
+
+    } #foreach
+
+    my $cust_main = new FS::cust_main ( \%cust_main );
+
+    use Tie::RefHash;
+    tie my %hash, 'Tie::RefHash'; #this part is important
+
+    my $invoicing_list = [];
+    my $error = $cust_main->insert( \%hash, $invoicing_list );
+
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "can't insert customer for $line: $error";
+    }
+
+    if ( $format eq 'simple' ) {
+
+      #false laziness w/bill.cgi
+      $error = $cust_main->bill( 'time' => $billtime );
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "can't bill customer for $line: $error";
+      }
+  
+      $cust_main->apply_payments;
+      $cust_main->apply_credits;
+  
+      $error = $cust_main->collect();
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "can't collect customer for $line: $error";
+      }
+
+    }
+
+    $imported++;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  return "Empty file!" unless $imported;
+
+  ''; #no error
+
+} # sub batch_import_onp
+
+
+# This import script was written to import and process 810 & 867 edi
+# data for customer
+# coder: Cal Tran
+# dob: 3/11/08
+
+=item batch_edidata_onp
+
+=cut
+
+sub batch_edidata_onp {
+  my $param = shift;
+  #warn join('-',keys %$param);
+  my $fh = $param->{filehandle};
+  my $agentnum = $param->{agentnum};
+
+  my $refnum = $param->{refnum};
+  my $pkgpart = $param->{pkgpart};
+
+  my $debug = 1;
+  #my @fields = @{$param->{fields}};
+  my $format = $param->{'format'};
+  my (@fields, @incoming_fields);
+  my $payby;
+  if ( $format eq 'simple' ) {
+    @fields = qw( cust_pkg.setup dayphone first last
+                  address1 address2 city state zip comments );
+    $payby = 'BILL';
+  } 
+  elsif ( $format eq 'extended' ) {
+
+  }
+  else {
+    die "unknown format $format";
+  }
+
+  eval "use Text::CSV_XS;";
+  die $@ if $@;
+
+  my $csv = new Text::CSV_XS;
+  #warn $csv;
+  #warn $fh;
+
+  my $imported = 0;
+  #my $columns;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $dbh = dbh;
+  my $return_msg;
+  
+  #while ( $columns = $csv->getline($fh) ) {
+  my $line;
+  while ( defined($line=<$fh>) ) {
+
+    $csv->parse($line) or do {
+      return "ERROR can't parse: ". $csv->error_input();
+    };
+
+    my @columns = $csv->fields();
+    my $inpstr = $debug ? join ('|', at columns) : '';
+    #warn join('-', at columns);
+
+    # getting rid of all leading and trailing spaces
+    foreach my $value (@columns) {
+      $value =~ s/^\s*(.*?)\s*$/$1/;
+    }
+
+    my $esiid = $columns[3];
+
+    ### check for matching of 810 usage and 867 usage
+    my $usage_match_810_867;
+    if ($columns[11] == $columns[31]) {
+      $usage_match_810_867 = 0;
+    } 
+    else {
+      $usage_match_810_867 = 'FALSE';
+    };
+
+
+    my @svc_external = qsearch ( 'svc_external', { 'id'    => $esiid } );
+
+    return "ERROR fail: No one own ESIID $esiid!" unless @svc_external;
+
+    my $cust_main;
+    my $cust_pkg;
+
+    foreach my $svcexternal (@svc_external) {
+      my $cust_svc  = qsearchs ( 'cust_svc', { 'svcnum'    => $svcexternal->svcnum} );
+      unless ($cust_svc) {
+        return "ERROR fail1";
+      }
+
+      $cust_pkg  = qsearchs ( 'cust_pkg', { 'pkgnum'    => $cust_svc->pkgnum} );
+      unless ($cust_pkg) {
+        return "ERROR fail2";
+      }
+
+      # don't process any package unless it is active
+      next if ($cust_pkg->status() ne "active");
+       
+      $cust_main  = qsearchs ( 'cust_main', { 'custnum'    => $cust_pkg->custnum} );
+      unless ($cust_main) {
+        return "ERROR fail3";
+        #return "can't insert customer for $line: $error";
+      }
+
+      # don't process any customer that is not active
+      next if ($cust_main->status() ne "active");
+
+      my $svc_num = $svcexternal->svcnum;
+      my $firstname = $cust_main->first;
+      my $lastname = $cust_main->last;
+      my $custnum = $cust_main->custnum;
+      my $balance = $cust_main->balance;
+      my $lastbilled = time2str('%D',$cust_pkg->get('last_bill'));
+
+      # note - the empty column that is after $lastbilled is later used tostore
+      # the last reading from usage_elec
+      my $easy_view = join(',',$usage_match_810_867, $firstname, $lastname, $custnum,
+                               $svc_num,$balance, $lastbilled,'--',
+                               '**',
+         # 13-start date 14-end date 28-prev read 29-curr read
+                               $columns[13], $columns[14], $columns[28], 
+         # 29-curr read 6-tdsp
+                               $columns[29], $columns[6],
+         # 30 - meter multiplier 31-867 usage 21-measured demand
+                               $columns[30], $columns[31], $columns[21], 
+         # 20 - billed demand
+                               $columns[20], '','','',
+                               '**',
+                               @columns
+         );
+
+      if ($return_msg) {
+        $return_msg .= "\n$easy_view";
+      }
+      else {
+        $return_msg = "$easy_view";
+      }
+
+    } #foreach svc_external
+
+  }
+
+  return($return_msg); 
+
+  ''; #no error
+
+} # sub batch_edidata_onp
+
+sub insert_test_value{
+  my $time = time;
+
+	 my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+	my $transaction810 = new FS::usage_elec({
+		'prev_date'=>'1',
+		'curr_date'=>'1',
+		'prev_read'=>'1',
+		'curr_read'=>'1',
+		'tdsp'=>'1',
+		'svcnum'=>'10',
+		'meter_multiplier'=>'1',
+		'measured_demand'=>'1',
+		'billed_demand'=>'1',
+
+		
+	});
+
+    my $error=$transaction810->insert;
+	if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "error applying prepaid card (transaction rolled back): $error";
+      
+    }
+}
+
 sub _upgrade_data { #class method
   my ($class, %opts) = @_;
 

Index: Schema.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/Schema.pm,v
retrieving revision 1.226
retrieving revision 1.226.2.1
diff -u -w -d -r1.226 -r1.226.2.1
--- Schema.pm	28 Jul 2010 23:16:29 -0000	1.226
+++ Schema.pm	2 Aug 2010 19:49:16 -0000	1.226.2.1
@@ -593,6 +593,38 @@
         'phonenum', 'varchar', 'NULL', 15, '', '',
         'regionname', 'varchar', 'NULL', $char_d, '', '',
         'detail',  'varchar', '', 255, '', '', 
+        # seems suboptimal to store below values here
+        'prev_date','int','NULL','','0','',
+        'curr_date','int','NULL','','0','',
+        'prev_read','decimal','NULL','14,4','0','',
+        'curr_read','decimal','NULL','14,4','0','',
+        'number_of_days','int','NULL','','','',
+        'energy_usage','decimal','NULL','14,4','','',
+        'tdsp','decimal','NULL','10,2','0','',
+        'taxes','decimal','NULL','10,2','','',
+        'gr_fee','decimal','NULL','10,2','0','',
+        'rate','decimal','NULL','10,6','','',
+        'discount1_rate','decimal','NULL','10,6','','',
+        'discount1_total','decimal','NULL','10,2','','',
+        'energy_base','decimal','NULL','10,2','','',
+        'energy_charge','decimal','NULL','10,2','','',
+        'setup_fee','decimal','NULL','10,2','0','',
+        'one_time_charge','decimal','NULL','10,2','0','',
+        'one_time_description','varchar','NULL','150','','',
+        'demanded_bill','decimal','NULL','14,4','0','',
+        'measured_bill','decimal','NULL','14,4','0','',
+        'meter_multiplier','real','NULL','','0','',
+        'balance', at money_type,'0','',
+        'average_price','decimal','NULL','10,4','0','',
+        'pkg_info','varchar','NULL','255','','',
+        'note','varchar','NULL','255','','',
+        'meter_number','varchar','NULL','255','','',
+        'esiid','varchar','NULL','255','','',
+        'late_fee','decimal','NULL','14,4','0','',
+        'last_pay', at money_type,'0','',
+        'last_pay_date','int','NULL','','','',
+        'return_addr','varchar','NULL',150,'','',
+        'bill_return_address','varchar','NULL',150,'','',
       ],
       'primary_key' => 'detailnum',
       'unique' => [],
@@ -2188,7 +2220,7 @@
     'svc_external' => {
       'columns' => [
         'svcnum', 'int', '', '', '', '', 
-        'id',     'int', 'NULL', '', '', '', 
+        'id',     'varchar', 'NULL', $char_d, '', '', 
         'title',  'varchar', 'NULL', $char_d, '', '', 
       ],
       'primary_key' => 'svcnum',
@@ -2196,6 +2228,116 @@
       'index'       => [],
     },
 
+    'usage_elec_transaction867' => {
+      'columns'=> [
+        'id','serial','','','','',
+       'usage_elec_id','serial','','','','',
+       'note','varchar','NULL','255','','',
+       ],
+       'primary_key'=> 'id',
+       'unique' => [],
+       'index'=>[['usage_elec_id']],
+      },
+
+    'usage_elec' => {
+      'columns' => [
+       'id', 'serial', '',      '', '', '',
+       'prev_date', @date_type, '', '',
+       'curr_date', @date_type,'','',
+       'prev_read', 'decimal', '14,4',     '', '', '',
+       'curr_read', 'decimal', '14,4',     '', '', '',
+       'tdsp', @money_type, '', '',
+       'meter_multiplier','real','','','','',
+       'total_usage','decimal','14,4','','','',
+       'measured_demand','decimal','14,4','','','',
+       'billed_demand','decimal','14,4','','','',
+       'svcnum', 'int', '', '', '', '',
+        '_date', at date_type,'','',
+        'meter_number','varchar','255','','','',
+      ],
+       'primary_key' => 'id',
+       'unique'      => [],
+        'index'       => [['svcnum']],
+     },
+
+# -nguyen
+    'elec_general' => {
+      'columns' => [
+       'id', 'serial', '',      '', '', '',
+       'esiid','int','','','','',
+      ],
+       'primary_key' => 'id',
+       'unique'      => [],
+        'index'       => [],
+     },
+
+    'trading_rep_profile' => {
+      'columns' => [
+       'id',                     'serial',     '',       '',         '', '',
+       'company_name',           'varchar',    'NULL',   $char_d,    '', '',
+       'duns_num',               'int',        '',       '',         '', '',
+       'company_type',           'varchar',    'NULL',   $char_d,    '', '',
+       'puct_license_num',       'int',        '',       '',         '', '',
+       'active',                 'int',        '',       '',         '', '',
+       'start_date',             @date_type,   '',       '',
+       'end_date',               @date_type,   '',       '',
+      ],
+       'primary_key' => 'id',
+       'unique'      => [ ['duns_num'] ],
+        'index'       => [],
+     },
+
+
+    'transaction810' => {
+      'columns' => [
+        'id',                     'serial',     '',       '',         '', '',
+        'tdsp_duns',              'varchar',    'NULL',   $char_d,    '', '',
+        'inv_num',                'varchar',    'NULL',   $char_d,    '', '',
+        'ref_identification',     'varchar',    'NULL',   $char_d,    '', '',
+        'esiid',                  'varchar',    'NULL',   $char_d,    '', '',
+        'tdsp',                   'int',        '',       '',         '', '',
+        'due_date',               'int',        '',       '',         '', '', 
+        'inv_date',               'int',        '',       '',         '', '',
+        'usage_kwatts',           'real',       '',       '',         '', '',
+        'srvc_from_date',         'int',        '',       '',         '', '',
+        'srvc_to_date',           'int',        '',       '',         '', '',
+        'puct_fund',              'int',        '',       '',         '', '',
+        'billed_demand',          'real',       '',       '',         '', '',
+        'measured_demand',        'real',       'NULL',   '',         '', '',
+        'bill_status',            'char',       '',       2,          '', '',
+        'type_of_bill',           'int',        '',       '',         '', '', # bool 0/1
+        'ack_997',                'int',        '',       '',         '', '', # bool 0/1
+        'processed',              'int',        '',       '',         '', '', # bool 0/1
+      ],
+       'primary_key' => 'id',
+       'unique'      => [ ['inv_num'] ],
+        'index'       => [ ['id'], ['tdsp_duns'], ['inv_num'], ['esiid'] ],
+     },
+
+     'transaction867' => {
+       'columns' => [
+       'id',                       'serial',     '',       '',         '', '',
+       'tdsp_duns',                'int',                  '',       '',   '', '',
+       'ref_identification',       'varchar',    '',       $char_d,    '', '',
+       'esiid',                    'varchar',    '',       $char_d,    '', '',
+       'trans_creation_date',      'int',        '',       '',       '', '', 
+       'meter_no',                 'varchar',    '',       $char_d,    '', '',
+       'srvc_period_start_date',   'int',        '',       '',         '', '',
+       'srvc_period_end_date',     'int',        '',       '',         '', '',
+       'prev_read_kwatts',         'real',       '',       '',         '', '',
+       'curr_read_kwatts',         'real',       '',       '',         '', '',
+       'meter_multiplier',         'real',       '',       '',         '', '',
+       'usage_kwatts',             'real',       '',       '',         '', '',
+       'measured_demand',          'real',       'NULL',   '',         '', '',
+       'ack_997',                  'int',        '',       '',         '', '', # bool 0/1
+       'processed',                'int',        '',       '',         '', '', # bool 0/1
+      ],
+       'primary_key' => 'id',
+       'unique'      => [ ['ref_identification'] ],
+        'index'       => [],
+     },
+
+
     'cust_pay_refund' => {
       'columns' => [
         'payrefundnum', 'serial', '', '', '', '', 
@@ -2844,6 +2986,21 @@
       'index'       => [ [ 'pkgnum' ], [ 'refnum' ] ],
     },
 
+#-- nguyen
+    'svc_elec' => {
+      'columns' => [
+        'id', 'serial', '',      '', '', '',
+        'esiid','int','','','','',
+        'svcnum',      'int',         '',      '', '', '',
+        'countrycode', 'varchar',     '',       3, '', '',
+        'phonenum',    'varchar',     '',      15, '', '',  #12 ?
+        'pin',         'varchar', 'NULL', $char_d, '', '',
+      ],
+      'primary_key' => 'svcnum',
+      'unique' => [],
+      'index'  => [ [ 'countrycode', 'phonenum' ] ],
+    },
+
     'svc_pbx' => {
       'columns' => [
         'svcnum',           'int',     '',      '', '', '', 

--- NEW FILE: elec_general.pm ---
package FS::elec_general;

use strict;
use vars qw( @ISA );
use FS::Record qw( qsearch qsearchs );

@ISA = qw(FS::Record);

=head1 NAME

FS::elec_general - Object methods for elec_general records

=head1 SYNOPSIS

  use FS::elec_general;

  $record = new FS::elec_general \%hash;
  $record = new FS::elec_general { 'column' => 'value' };

  $error = $record->insert;

  $error = $new_record->replace($old_record);

  $error = $record->delete;

  $error = $record->check;

=head1 DESCRIPTION

An FS::elec_general object represents an example.  FS::elec_general inherits from
FS::Record.  The following fields are currently supported:

=over 4

=item id - primary key

=item esiid - 


=back

=head1 METHODS

=over 4

=item new HASHREF

Creates a new example.  To add the example 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 { 'elec_general'; }

=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 example.  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('id')
    || $self->ut_number('esiid')
  ;
  return $error if $error;

  $self->SUPER::check;
}

=back

=head1 BUGS

The author forgot to customize this manpage.

=head1 SEE ALSO

L<FS::Record>, schema.html from the base documentation.

=cut

1;


--- NEW FILE: usage_elec.pm ---
package FS::usage_elec;

use strict;
use vars qw( @ISA @EXPORT_OK $me);
use FS::Record qw( qsearch qsearchs );
use FS::UID qw( getotaker dbh );
use FS::usage_elec_transaction867;
#use FS::cust_main;
use Exporter;
use List::Util qw[min max];
use Date::Format;
use HTTP::Date qw( str2time );
use Data::Dumper;
use Date::Calc qw(Delta_Days);
@ISA = qw(FS::Record Exporter);

@EXPORT_OK = qw( most_current_date curr_read edi_to_usage );

=head1 NAME

FS::usage_elec - Object methods for usage_elec records

=head1 SYNOPSIS

  use FS::usage_elec;

  $record = new FS::usage_elec \%hash;
  $record = new FS::usage_elec { 'column' => 'value' };

  $error = $record->insert;

  $error = $new_record->replace($old_record);

  $error = $record->delete;

  $error = $record->check;

=head1 DESCRIPTION

An FS::usage_elec object represents an example.  FS::usage_elec inherits from
FS::Record.  The following fields are currently supported:

=over 4

=item id - primary key

=item prev_date - 

=item curr_date - 

=item prev_read - 

=item curr_read - 

=item tdsp - 

=item svcnum - 

=item _date - 


=back

=head1 METHODS

=over 4

=item new HASHREF

Creates a new example.  To add the example 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 { 'usage_elec'; }

=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 example.  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('id')
    || $self->ut_numbern('prev_date')
    || $self->ut_numbern('curr_date')
    || $self->ut_number('prev_read')
    || $self->ut_number('curr_read')
    || $self->ut_money('tdsp')
    || $self->ut_number('svcnum')
    || $self->ut_numbern('_date')
    || $self->ut_float('meter_multiplier')
    || $self->ut_numbern('demand_measure')
    || $self->ut_numbern('demand_bill')
  ;
  return $error if $error;

  $self->SUPER::check;
}

=back

=head1 BUGS

The author forgot to customize this manpage.

=head1 SEE ALSO

L<FS::Record>, schema.html from the base documentation.

=cut
sub most_current_date {
 # my $self = shift;
  my $cust_nr=shift;
  my @custs = qsearch('usage_elec',{ 'cust_nr' => $cust_nr});

  my $most_current_date  = 0;
  
  if (@custs) {
    
    foreach my $cust (@custs) {
       if ($cust->curr_date > $most_current_date){
		$most_current_date = $cust;   
	}
    }
  }
  
  return $most_current_date;

}

sub getUsage{
        my $self = shift;
	return $self->total_usage;
}
#sub getUsage{
#	my $self = shift;
#    my $prev_read=$self->prev_read;
#	my $curr_read=$self->curr_read;
#        my $usage;
#	if ($prev_read<=$curr_read) {
#		$usage= ($curr_read-$prev_read);
#	}
#	else{
#		$usage=(($curr_read+10**max(length($prev_read),length($curr_read)))-$prev_read);
#	}
#	return $usage*$self->meter_multiplier;
#}

sub getNumberOfDays {
  my $self = shift;
  return Date::Calc::Delta_Days( time2str('%Y', $self->prev_date), 
                                 time2str('%L', $self->prev_date),
                                 time2str('%e', $self->prev_date), 
                                 time2str('%Y', $self->curr_date),
			         time2str('%L', $self->curr_date), 
                                 time2str('%e', $self->curr_date)
                               );
}


### insert into table
#
sub insert_usage {
  my $self = shift;
 
  my $debug = 0;
  my $error;
  
  local $SIG{HUP} = 'IGNORE';
  local $SIG{INT} = 'IGNORE';
  local $SIG{QUIT} = 'IGNORE';
  local $SIG{TERM} = 'IGNORE';
  local $SIG{TSTP} = 'IGNORE';
  local $SIG{PIPE} = 'IGNORE';
 
  my $oldAutoCommit = $FS::UID::AutoCommit;
  local $FS::UID::AutoCommit = 0;
  my $dbh = dbh;

  $error = $self->check;
  return $error if $error;

  $error = $self->SUPER::insert;
  if ( $error ) {
    $dbh->rollback if $oldAutoCommit;
    my $msg = "error: Can't insert data into usage_elec : $error\n"
             .Dumper($self);
    return $msg;
  }
 
  $dbh->commit or die $dbh->errstr if $oldAutoCommit;

  return;
}

### Take in a time and convert it to time string to be entered into usage_elec
### the function use is str2time from module "HTTP::Date qw( str2time )"
sub to_usage_elec_time {
  my ($time) = shift;

  ### becareful using time2str, year allows are 1970-jan2038

  return str2time($time);
}


# Get the past 10 usage for a particular svcnum and return the object
# return:
#  array of usages object
#  undef otherwise
#
#

sub query_usage {
  my ($svcnum, $how_many) = @_;

  #$how_many = 10 unless $how_many; # default to 10 usages

#  my @usages  = qsearch ( 
#                          'usage_elec', 
#                          {
#
#                           'svcnum' => $svcnum,
#                           # sort in DESCending order so it easier to splice
#                           # the array in the next step
#                           'extra_sql' => 'ORDER BY _date DESC'
#                          }
#                        );

  my @usages = qsearch ( {
                          'table'   => 'usage_elec',
                          'hashref' => { 'svcnum' => $svcnum },
                          'extra_sql' => 'ORDER BY _date DESC'
                         } );

  # shrink the array to $how_many index if it over the requested number
  $#usages = $how_many - 1 if ( @usages && $how_many && (@usages > $how_many) );
    

  if (@usages) {
    # since we query the usage by DESCending order, it a good idea to put it 
    # in ascending order before a return.
    @usages = reverse @usages;
    return @usages;
  }

  return;
}


# sub routine that go through the transaction810 and transaction867 table
# to put data into usage_elec table
#
#
# some note
# to input data into usage_elect, all condition below must be meet
# 1. there is unprocess data from transaction810 table
# 2. there is unprocess data from transaction867 table
# 3. the unprocess data from transaction867 match transaction810
#    data.

sub edi_to_usage {
  my $self = shift;

  my $debug = 1;
  #my @invoices_to_generate; # store usage_elec svcnum 

  # Only send data to usage_elec if a transactin from 810 & 867 match up
  #
 
  # first thing first.  Let get all edi from transaction_810 table that haven't
  # been process
  my @edi_810_processeds = qsearch ( 
                            'transaction810', 
                            {'processed' => '0'}
                          );

  unless (@edi_810_processeds) {
    return "There were no un-process 810 to input into usage_elec.\n"
          ."Run again when there is 810 data to process\n";
  }

  # second, let get all edi from transaction_867 table that haven't been 
  # process
  my @edi_867_processeds = qsearch ( 
                            'transaction867', 
                            {'processed' => '0'}
                          );

  unless (@edi_867_processeds) {
    return "There were no un-process 867 to match up with 810 data.\n"
          ."Run again when there is 867 data to process\n";
  }

  # third, match up the 810 and 867 data.  Those data that match up, goes
  # into usage_elec table.

  ### for efficientcy we will use the smaller list to traverse
  if (@edi_810_processeds < @edi_867_processeds) {
    
    print "debug: using 810\n" if $debug;

    foreach my $edi_810 (@edi_810_processeds) {
      # find matching 867
      my $ref_identification_810 = $edi_810->ref_identification;
      my $srv_from_810 = $edi_810->srvc_from_date;
      my $srv_to_810 = $edi_810->srvc_to_date;
      ### search for the edi that match exactly with the 810
      my $edi_867 = qsearchs ( 'transaction867',
                               { 'ref_identification' => $ref_identification_810,
                                 'srvc_from_date'     => $srv_from_810,
                                 'srvc_to_date'       => $srv_to_810,
                               }
                            );
      if ($edi_867) {
        ### we have a match, extract the data and put into usage
        my $usage_elec_obj = extract_data_to_usage_elec ($edi_810, $edi_867);
        if ($usage_elec_obj) {

          ### mark the 810 and 867 as already process
          $edi_810->setfield('processed',1);
          $edi_867->setfield('processed',1);

          ### go ahead and billed 
          my $rtnval = billing_call($usage_elec_obj);
          if ($rtnval) {
            print "Oh! Oh!.. unable to bill svcnum: $usage_elec_obj->svcnum\n";
            print $rtnval;
            $edi_810->setfield('processed',0);
            $edi_867->setfield('processed',0);
            $usage_elec_obj->delete;
            return;
          }

        }
        else {
          print "RED ALERT.. something went wrong when inserting data\n"
               ."into usage_elec (810)\n";
          print "ref_identification of 810 : " . $edi_867->ref_identification
               ."\n";
          return;
        }

      }
    }
    
  }
  else {

    print "debug: using 867\n" if $debug;

    foreach my $edi_867 (@edi_867_processeds) {
      # find matching 810
      my $ref_identification_867 = $edi_867->ref_identification;
      my $srv_from_867 = $edi_867->srvc_period_start_date;
      my $srv_to_867 = $edi_867->srvc_period_end_date;
      print "(debug) ref_identification: $ref_identification_867\n" if $debug;
      ### search for the edi that match exactly with the 867
      my $edi_810 = qsearchs ( 'transaction810',
                               { 'ref_identification' => $ref_identification_867,
                                 'srvc_from_date'     => $srv_from_867,
                                 'srvc_to_date'       => $srv_to_867,
                               }
                             );
      if ($edi_810) {

        print "(debug) found an 810 that match the 867: esiid "
             .$edi_810->esiid."\n" if $debug;

        ### we have a match, extract the data and put into usage
        my $usage_elec_obj = extract_data_to_usage_elec($edi_810, $edi_867);
        if ($usage_elec_obj) {

          ### mark the 810 and 867 as already process
          my $edi_810_new = new FS::transaction810( { $edi_810->hash } );
          $edi_810_new->setfield('processed',1);
          my $error = $edi_810_new->replace($edi_810);
          if ($error) {
            print "there is an error changing column 'processed' of transaction810 table\n";
            print "error: $error\n";
          }

          my $edi_867_new = new FS::transaction867( { $edi_867->hash } );
          $edi_867->setfield('processed',1);
          $error = $edi_867_new->replace($edi_867);
          if ($error) {
            print "there is an error changing column 'processed' of transaction867 table\n";
            print "error: $error\n";
          }

          ### go ahead and billed 
          my $rtnval = billing_call($usage_elec_obj);
          if ($rtnval) {
            print "Oh! Oh!.. unable to bill svcnum: $usage_elec_obj->svcnum\n";
            print "$rtnval";
            $edi_810->setfield('processed',0);
            $edi_867->setfield('processed',0);
            $usage_elec_obj->delete;
            return;
          }
        }
        else {
          print "RED ALERT.. something went wrong when inserting data\n"
               ."into usage_elec (810)\n";
          print "ref_identification of 810 : " . $edi_810->ref_identification
               ."\n";
          #return;
        }
      }
    }

  }


}

# This subroutine does the physical adding of data into usage_elec
# using the transaction810 and transaction867 table

sub extract_data_to_usage_elec {
  my ($edi_810, $edi_867) = @_;

  ### variables declaration
  ### following decl are column of usage_elec
  my ($prev_date, $curr_date, $prev_read, $curr_read, $tdsp, $svcnum, $_date,
      $meter_multiplier, $total_usage, $measured_demand, $billed_demand,
      $meter_number);

  local $SIG{HUP} = 'IGNORE';
  local $SIG{INT} = 'IGNORE';
  local $SIG{QUIT} = 'IGNORE';
  local $SIG{TERM} = 'IGNORE';
  local $SIG{TSTP} = 'IGNORE';
  local $SIG{PIPE} = 'IGNORE';

  my $oldAutoCommit = $FS::UID::AutoCommit;
  local $FS::UID::AutoCommit = 0;
  my $dbh = dbh;

  ### this message will print in the year 2038 because their is a limitation
  #   with str2time ( cpan.org package Icwa-1.0.0.tar.gz )
  if ( int(time2str('%Y',time)) > 2037 ) {
    print "Bug: Try to use function 'time2str' has generate an error because"
         ."\n\tit can't handle year greter than 2037.\n";
    return;
  }   

  # data from 867
  $prev_date = str2time($edi_867->srvc_period_start_date);
  $curr_date = str2time($edi_867->srvc_period_end_date);
  $prev_read = $edi_867->prev_read_kwatts;
  $curr_read = $edi_867->curr_read_kwatts;
  $meter_multiplier = $edi_867->meter_multiplier;
  $total_usage = $edi_867->usage_kwatts;
  $measured_demand = $edi_867->measured_demand;
  $meter_number = $edi_867->meter_no;

  # data from 810
  $tdsp = sprintf('%.2f',$edi_810->tdsp/100);
  $billed_demand = $edi_810->billed_demand;

 
  ### obtain the svcnum
  my $esiid = $edi_810->esiid;
  my $svc_obj = qsearchs (  'svc_external',
                           { 'id' => $esiid
                           }
                         );
  return unless ($svc_obj); #debug
  $svcnum = $svc_obj->svcnum;

  ### obtain _date
  $_date = time;


  ### got everything we needed
  #   now let insert it into usage_elec
  my %usage = (
                'prev_date'        =>   $prev_date,
                'curr_date'        =>   $curr_date,
                'prev_read'        =>   $prev_read,
                'curr_read'        =>   $curr_read,
                'tdsp'             =>   $tdsp,       
                'svcnum'           =>   $svcnum,
                '_date'            =>   $_date,
                #'meter_multiplier' =>   $meter_multiplier,
                'meter_multiplier' =>   $meter_multiplier,
                'total_usage'      =>   $total_usage,
                'measured_demand'  =>   $measured_demand,
                'billed_demand'    =>   $billed_demand,
                'meter_number'     =>   $meter_number,
              );
  print "usage_elect Dumping". Dumper(\%usage);
 
  if ( $edi_810->esiid != '10443720004466311' &&
       $edi_810->esiid != '10443720004264904') {
    return; #for testing
  }
  
  my $usage_elec_obj = new FS::usage_elec( \%usage );
  my $error = $usage_elec_obj->insert;
  print "I'm inserting something into usage_elec\n";
  if ( $error ) {
    $dbh->rollback if $oldAutoCommit;
    my $msg = "Can't insert data into usage_elec : $error\n"
             .Dumper(\%usage);
    print "$msg";
    return; 
  }
 
  $dbh->commit or die $dbh->errstr if $oldAutoCommit;

  ### for testing purpose.. put a message into usage_elec_transaction_867
  #
  my $usage_elec_transaction867_obj = new FS::usage_elec_transaction867( 
         {'usage_elec_id' => $usage_elec_obj->id,
          'note' => "Attention: a meter change out has occured at"
                   ." your location\n"
         } 
        );

  $error = $usage_elec_transaction867_obj->insert;
  print "Adding note into usage_elec_transaction867\n";
  if ( $error ) {
    $dbh->rollback if $oldAutoCommit;
    my $msg = "Can't insert data into usage_elec_transaction867 : $error\n";
    print "$msg";
    return; 
  }
 
  $dbh->commit or die $dbh->errstr if $oldAutoCommit;


  #exit;
  return $usage_elec_obj;

} # end extract_data_to_usage_elec 

### do the billing call
### return: if there is an error, returns the error, otherwise
###         returns false.
sub billing_call {
  my $usage_elec_obj = shift;

  my $debug = 0;

  my $svcnum = $usage_elec_obj->svcnum;
  print "svcnum = $svcnum\n" if $debug;

  my $package = qsearchs (  'cust_svc', 
                           { 'svcnum' => $svcnum
                           }
                         );
  unless ($package) {
    return "error: sub billing_call: unable to acquire the package\n";
  }
  my $pkgnum = $package->pkgnum;
  print "pkgnum = $pkgnum\n" if $debug;

  my $custpkg = qsearchs (  'cust_pkg',
                           { 'pkgnum'  => $pkgnum
                           }
                         ); 
  unless ($custpkg) {
    return "error: sub billing_call: unable to acquire the custpkg\n";
  }
  my $custnum = $custpkg->custnum;
  print "custnum = $custnum\n" if $debug;

  my $cust_main_obj = qsearchs (  'cust_main',
                                 { 'custnum'  => $custnum
                                 }
                               );  
  unless ($cust_main_obj) {
    return "error: sub billing_call: unable to acquire the cust_main_obj\n";
  }

  my $rtnval = $cust_main_obj->bill();
  if ($rtnval) {
    return "error: calling billing command\n\t$rtnval";
  }

  ### now let generate the invoice for the customer
  if ($debug) { #debug
    my $heading = "\tid\tprev_date\tcurr_date\tprev_read\tcurr_read"
                . "\ttdsp\tsvcnum\t_date\n";

    print "$heading";
    print "\t" . $usage_elec_obj->id; 
    print "\t" . $usage_elec_obj->prev_date; 
    print "\t" . $usage_elec_obj->curr_date; 
    print "\t" . $usage_elec_obj->prev_read; 
    print "\t" . $usage_elec_obj->curr_read; 
    print "\t" . $usage_elec_obj->tdsp; 
    print "\t" . $usage_elec_obj->svcnum; 
    print "\t" . $usage_elec_obj->_date; 
    print "\t" . $usage_elec_obj->meter_multiplier;
    print "\t" . $usage_elec_obj->measured_demand;
    print "\t" . $usage_elec_obj->billed_demand;
    print "\n";
  }

  return;

} #end billing_call

1;


Index: Mason.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/Mason.pm,v
retrieving revision 1.47
retrieving revision 1.47.2.1
diff -u -w -d -r1.47 -r1.47.2.1
--- Mason.pm	21 Jul 2010 02:06:44 -0000	1.47
+++ Mason.pm	2 Aug 2010 19:49:16 -0000	1.47.2.1
@@ -248,6 +248,12 @@
   use FS::rate_time_interval;
   use FS::msg_template;
   use FS::part_tag;
+  use FS::elec_general;
+  use FS::svc_elec;
+  use FS::usage_elec;
+  use FS::transaction810;
+  use FS::transaction867;
+  use FS::usage_elec_transaction867;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {



More information about the freeside-commits mailing list