add svc_elec_features merged from reference code RT#7643 svc_elec_features
authorjeff <jeff>
Mon, 2 Aug 2010 19:49:26 +0000 (19:49 +0000)
committerjeff <jeff>
Mon, 2 Aug 2010 19:49:26 +0000 (19:49 +0000)
46 files changed:
FS/FS/Conf.pm
FS/FS/Mason.pm
FS/FS/Schema.pm
FS/FS/cust_bill.pm
FS/FS/cust_bill_pkg.pm
FS/FS/cust_bill_pkg_detail.pm
FS/FS/cust_main.pm
FS/FS/elec_general.pm [new file with mode: 0755]
FS/FS/part_pkg.pm
FS/FS/part_pkg/business_elec_generic.pm [new file with mode: 0755]
FS/FS/part_pkg/energy_base_discount_500kwh.pm [new file with mode: 0755]
FS/FS/part_pkg/energy_base_discount_tiers.pm [new file with mode: 0755]
FS/FS/part_pkg/residential_elec_generic.pm [new file with mode: 0755]
FS/FS/part_pkg/residential_elec_generic_var.pm [new file with mode: 0755]
FS/FS/svc_elec.pm [new file with mode: 0755]
FS/FS/transaction810.pm [new file with mode: 0755]
FS/FS/transaction867.pm [new file with mode: 0755]
FS/FS/usage_elec.pm [new file with mode: 0755]
FS/FS/usage_elec_transaction867.pm [new file with mode: 0755]
FS/MANIFEST
FS/t/elec_general.t [new file with mode: 0755]
FS/t/svc_elec.t [new file with mode: 0755]
FS/t/transaction810.t [new file with mode: 0755]
FS/t/transaction867.t [new file with mode: 0755]
FS/t/usage_elec.t [new file with mode: 0755]
FS/t/usage_elec_transaction867.t [new file with mode: 0755]
conf/rec_latex [new file with mode: 0755]
httemplate/edit/process/usage_elec_manual_input.cgi [new file with mode: 0755]
httemplate/edit/usage_elec_manual_input.cgi [new file with mode: 0755]
httemplate/elements/menu.html
httemplate/index.html
httemplate/misc/cust_edi_data-onp.cgi [new file with mode: 0755]
httemplate/misc/cust_main-import-oldonp.cgi [new file with mode: 0755]
httemplate/misc/payment.cgi
httemplate/misc/process/cust_edi_data-onp.cgi [new file with mode: 0755]
httemplate/misc/process/cust_main-import-oldonp.cgi [new file with mode: 0755]
httemplate/misc/process/qualified_liteup_customers.cgi [new file with mode: 0755]
httemplate/misc/process/transaction810-import.cgi [new file with mode: 0755]
httemplate/misc/qualified_liteup_customers.cgi [new file with mode: 0755]
httemplate/misc/transaction810-import.cgi [new file with mode: 0755]
httemplate/misc/usage_elec_prefilled_input.cgi [new file with mode: 0755]
httemplate/pref/pref-process.html
httemplate/pref/pref.html
httemplate/view/cust_bill-pdf.cgi
httemplate/view/cust_bill.cgi
httemplate/view/svc_external.cgi

index ce8bd29..44140ec 100644 (file)
@@ -1156,6 +1156,13 @@ and customer address. Include units.',
   },
 
   {
+    '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 @@ and customer address. Include units.',
     '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 @@ and customer address. Include units.',
   },
 
   {
+    '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 0f14150..f75633a 100644 (file)
@@ -248,6 +248,12 @@ if ( -e $addl_handler_use_file ) {
   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 ) {
index 60d2bce..3124f81 100644 (file)
@@ -593,6 +593,38 @@ sub tables_hashref {
         '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',@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',@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 @@ sub tables_hashref {
     '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 @@ sub tables_hashref {
       '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',@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 @@ sub tables_hashref {
       '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',     '',      '', '', '', 
index 4bd9aa1..fcc2a65 100644 (file)
@@ -36,6 +36,8 @@ use FS::part_bill_event;
 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 @@ FS::UID->install_callback( sub {
   $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 @@ sub print_latex {
   $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 @@ Non optional options include
 
 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 @@ sub print_generic {
   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 @@ sub print_generic {
 
   #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 @@ sub print_generic {
     '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 @@ sub print_generic {
 
   );
 
+  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 @@ sub print_generic {
   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 @@ sub print_generic {
   $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 @@ sub print_generic {
   }
 
   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 @@ sub print_generic {
       $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 @@ sub print_generic {
                  );
     }
 
+
     if ( $section->{'description'} ) {
-      push @buf, ( ['','-----------'],
+      push @buf, ( ['', $dash],
                    [ $section->{'description'}. ' sub-total',
                       $money_char. sprintf("%10.2f", $section->{'subtotal'})
                    ],
@@ -2757,7 +2935,7 @@ sub print_generic {
   }
   $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 @@ sub print_generic {
     }else{
       push @total_items, $total;
     }
-    push @buf,['','-----------'];
+    push @buf,['', $dash];
     push @buf,[$item,
                $money_char.
                sprintf( '%10.2f', $amount )
@@ -2886,7 +3064,7 @@ sub print_generic {
       }else{
         push @total_items, $total;
       }
-      push @buf,['','-----------'];
+      push @buf,['', $dash];
       push @buf,[$self->balance_due_msg, $money_char. 
         sprintf("%10.2f", $balance_due ) ];
     }
index d396f82..0a1d422 100644 (file)
@@ -145,16 +145,22 @@ sub insert {
 
   if ( $self->get('details') ) {
     foreach my $detail ( @{$self->get('details')} ) {
-      my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail {
-        'billpkgnum' => $self->billpkgnum,
-        'format'     => (ref($detail) ? $detail->[0] : '' ),
-        'detail'     => (ref($detail) ? $detail->[1] : $detail ),
-        'amount'     => (ref($detail) ? $detail->[2] : '' ),
-        'classnum'   => (ref($detail) ? $detail->[3] : '' ),
-        'phonenum'   => (ref($detail) ? $detail->[4] : '' ),
-        'duration'   => (ref($detail) ? $detail->[5] : '' ),
-        'regionname' => (ref($detail) ? $detail->[6] : '' ),
-      };
+      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 ),
+          'amount'     => (ref($detail) ? $detail->[2] : '' ),
+          'classnum'   => (ref($detail) ? $detail->[3] : '' ),
+          'phonenum'   => (ref($detail) ? $detail->[4] : '' ),
+          '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 @@ sub cust_bill_pkg_detail {
   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 4d9ee81..14b85cd 100644 (file)
@@ -57,6 +57,45 @@ inherits from FS::Record.  The following fields are currently supported:
 
 =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,7 +180,33 @@ sub check {
     || $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 @@ sub _upgrade_data { # class method
                          'hashref' => {},
                          'extra_sql' => 'WHERE invnum IS NOT NULL AND '.
                                         'pkgnum IS NOT NULL',
+                         'order_by' => 'ORDER BY detailnum',
                       });
 
     if (scalar(@cbpd)) {
index 002b0c1..19478e1 100644 (file)
@@ -22,6 +22,7 @@ use Data::Dumper;
 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::type_pkgs;
 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 @@ sub _cust_pkg {
 # 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,22 +2881,33 @@ sub bill {
 
   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.
   ###
 
-  my %total_setup   = map { my $z = 0; $_ => \$z; } @passes;
-  my %total_recur   = map { my $z = 0; $_ => \$z; } @passes;
+  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 @@ sub bill {
                             '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 @@ sub bill {
 
   } #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 @@ sub bill {
                                 '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 @@ sub bill {
       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 @@ sub bill {
 
     }
 
-    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 @@ sub bill {
       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 @@ sub _make_lines {
   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 @@ sub _make_lines {
   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 @@ sub _make_lines {
     $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 @@ sub _make_lines {
   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 @@ sub _make_lines {
     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 @@ sub _make_lines {
         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 @@ sub _make_lines {
         #$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 @@ sub _make_lines {
       
   } #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 @@ sub batch_charge {
     }
 
     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 @@ sub process_bill_and_collect {
   $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 ('|',@columns) : '';
+    #warn join('-',@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 ('|',@columns) : '';
+    #warn join('-',@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) = @_;
 
diff --git a/FS/FS/elec_general.pm b/FS/FS/elec_general.pm
new file mode 100755 (executable)
index 0000000..293d013
--- /dev/null
@@ -0,0 +1,121 @@
+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;
+
index f278d5e..92b2b23 100644 (file)
@@ -1418,6 +1418,7 @@ foreach my $INC ( @INC ) {
       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;
@@ -1432,8 +1433,16 @@ tie %plans, 'Tie::IxHash',
   sort { $info{$a}->{'weight'} <=> $info{$b}->{'weight'} }
   keys %info;
 
-sub plan_info {
-  \%plans;
+sub plan_info { 
+  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;
 }
 
 
diff --git a/FS/FS/part_pkg/business_elec_generic.pm b/FS/FS/part_pkg/business_elec_generic.pm
new file mode 100755 (executable)
index 0000000..14d8070
--- /dev/null
@@ -0,0 +1,96 @@
+package FS::part_pkg::business_elec_generic;
+#test
+use strict;
+use vars qw(@ISA %info %gr_fee %penalty_fee $DEBUG);
+use DBI;
+use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+use FS::usage_elec;
+
+@ISA = qw(FS::part_pkg::flat);
+$DEBUG = 0;
+
+tie %penalty_fee,'Tie::IxHash',
+  '0'=>0,
+  '0.05'=>0.05,
+;
+
+tie %gr_fee, 'Tie::IxHash',
+  '0.005' => 0.005,
+  '0.01'  => 0.01,
+  '0.02' => 0.02,
+;
+
+
+%info = (
+  'name' => 'Business base package',
+  'svc_elec_compatible' => 1,
+  'fields' => {
+    'setup_fee' => { 'name' => 'Setup fee for this package',
+                     'default' => 0,
+                   },
+    'base_fee' => { 'name' => 'Base fee for this package',
+                      'default' => 0,
+                    },
+    'rate' => { 'name' => 'Rate for customer',
+                               'default' => 1,
+                             },
+    'gr_fee' => { 'name' =>'Ground fee',
+                                 'type' =>'select',
+                         'select_options' => \%gr_fee,
+       },
+    'penalty' => { 'name'=>'Late fee',
+                  'type' =>'select',
+                  'select_options'=> \%penalty_fee,
+        },
+  },
+  'fieldorder' => [ 'setup_fee', 'base_fee','rate','gr_fee','penalty' ],
+ 'weight' => '70',
+);
+
+sub calc_recur {
+  my($self, $cust_pkg ) = @_;
+  my $date =0;
+  my  $cust_svc=qsearchs('cust_svc',{'pkgnum' => $cust_pkg->pkgnum});
+  my $lastdate =$cust_pkg -> last_bill ||0;
+  warn $lastdate if $DEBUG;
+  my  @usage_elecs=qsearch('usage_elec',{'svcnum' => $cust_svc->svcnum,
+                                        '_date'=> { op=>'>', value=>$lastdate },
+                              'extra_sql' => 'ORDER BY _date_'});
+
+  warn "test".@usage_elecs."\n" if $DEBUG;
+  if(defined($usage_elecs[0])){
+       warn "test2".$usage_elecs[0]->id if $DEBUG;
+       my $base=$self->option('base_fee');
+       my $rate=$self->option('rate');
+       my $sum= $base + ($usage_elecs[0]->getUsage)*$rate+$usage_elecs[0]->tdsp;
+       warn $sum."\n" if $DEBUG;
+       warn "grfee = ".$sum* $self->option('gr_fee') if $DEBUG;
+       $sum = $sum + $sum * $self->option('gr_fee');
+       warn "sum = ".$sum if $DEBUG;
+       return round($sum);
+       }
+  return 0;  
+  #$hours -= $self->option('recur_included_hours');
+  #$hours = 0 if $hours < 0;
+
+  #$self->option('recur_flat') + $hours * $self->option('recur_hourly_charge');
+  #return 99;
+}
+
+
+sub is_free_options {
+  qw( setup_fee recur_flat recur_unit_charge );
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('base_fee');
+}
+sub round {
+    my($number) = shift;
+    my $roundit= int($number*100 + .5);
+       return sprintf('%.2f',$roundit/100)
+}
+
+1;
diff --git a/FS/FS/part_pkg/energy_base_discount_500kwh.pm b/FS/FS/part_pkg/energy_base_discount_500kwh.pm
new file mode 100755 (executable)
index 0000000..22f3f58
--- /dev/null
@@ -0,0 +1,150 @@
+package FS::part_pkg::energy_base_discount_500kwh;
+
+use strict;
+use vars qw(@ISA %info %penalty_fee $DEBUG);
+use DBI;
+use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+use FS::usage_elec;
+use Date::Format;
+use Date::Parse;
+use Data::Dumper;
+
+
+@ISA = qw(FS::part_pkg::flat);
+$DEBUG = 0;
+
+tie %penalty_fee,'Tie::IxHash',
+  '0'=>0,
+  '0.05'=>0.05,
+;
+
+
+%info = (
+  'name'       => 'Energy base discount 500KWH',
+  'svc_elec_compatible' => 1,
+  'fields'     => 
+       {
+         'description'    => 
+             { 'name'    => 'Description printed on bill',
+               'default' => 'SPECIAL BASE CHARGE DISCOUNT FOR USAGE > 500KWH',
+             },
+         'rate'=>
+             { 'name'    => 'Discount Amount',
+               'default' => 4.95,
+             },
+         'penalty'        => 
+             { 'name'=>'Late fee',
+               'type' =>'select',
+               'select_options'=> \%penalty_fee,
+             },
+       },
+  'fieldorder' => [ 'description', 'rate' ],
+  'weight' => '70',
+);
+
+sub calc_recur {
+  my($self, $cust_pkg ) = @_;
+  my $date =0;
+
+  warn "cust_pkg = '\n" .Dumper($cust_pkg). "'\n" if $DEBUG;
+
+  #my  $cust_svc=qsearchs('cust_svc',{'pkgnum' => $cust_pkg->pkgnum});
+  #my $lastdate =$cust_pkg -> last_bill ||0;
+
+  # this fee is dependent on the existence of a base elecusage package existence
+  # so let check if it exist.
+  my $custnum = $cust_pkg->custnum;
+  my $basic_engpkg_exist;
+  my $usage_svcnum;
+  my $lastdate;
+
+  foreach my $cust_pkg_tmp ( qsearch(
+                              {
+                               'table'  => 'cust_pkg',
+                               'hashref'=> { 'custnum' => $custnum },
+                               'extra_sql' => 'ORDER BY pkgnum ASC' 
+                              } )
+                           ) {
+    next if $cust_pkg_tmp->getfield('cancel');
+    # -ctran 06/09/08
+    # updated liteup
+    next if $cust_pkg_tmp->getfield('susp');
+    next if ($cust_pkg_tmp->getfield('pkgnum') == $cust_pkg->pkgnum);
+
+    my $pkgnum = $cust_pkg_tmp->getfield('pkgnum');
+    warn "\tpkgnum = ". $pkgnum . "\n" if $DEBUG;
+
+    my $cust_svc_tmp = qsearchs('cust_svc',{'pkgnum' => $pkgnum});
+    warn "\t\tcust_svc_tmp = '" . Dumper($cust_pkg_tmp) . "'\n" if $DEBUG;
+
+    #check for keyword ESIID from svc_external
+    if ($cust_svc_tmp) {
+      my $svc_external = qsearchs('svc_external',{'svcnum'=>$cust_svc_tmp->svcnum});
+
+      if ($svc_external) {
+        warn "\t\t\tsvc_external = '" . Dumper($svc_external) . "'\n" if $DEBUG;
+        if (!$basic_engpkg_exist && ($svc_external->title =~ /^ESIID$/i)) {
+          $basic_engpkg_exist = 1;
+          $usage_svcnum = $cust_svc_tmp->getfield('svcnum');
+          $lastdate =$cust_pkg_tmp->last_bill ||0;
+        }
+      }
+    }
+    
+  }
+
+  warn "custnum = " . $custnum . "\n" if $DEBUG;
+  warn "lastdate='".time2str("%C",$lastdate)."'\n" if $DEBUG;
+  warn "lastdate='".$lastdate."'\n" if $DEBUG;
+  warn "usage_svcnum=".$usage_svcnum."\n" if $DEBUG;
+  warn "basic_engpkg_exist = " . $basic_engpkg_exist . "\n" if $DEBUG;
+
+  # now let get the usage if a energy package exist
+  if ($basic_engpkg_exist) {
+    my  @usage_elecs=qsearch(
+                       {
+                        'table'    => 'usage_elec',
+                        'hashref'  => { 'svcnum'  => $usage_svcnum,
+    #                                    '_date'   => { 'op' => '>',
+    #                                                   'value' => $lastdate
+    #                                                 }
+                                      },
+                        'extra_sql' => 'ORDER BY _date DESC'
+                       });
+
+    
+    if(defined($usage_elecs[0])) {
+       #warn "test2".@usage_elecs[0]->id."\n" if $DEBUG;
+       warn "usage = " . $usage_elecs[0]->getUsage."\n" if $DEBUG;
+       #my   $base=$self->option('base_fee');
+       #my   $rate=$self->option('rate');
+       #my   $sum= $base + (@usage_elecs[0]->getUsage)*$rate+@usage_elecs[0]->tdsp;
+          if ($usage_elecs[0]->getUsage >= 500) {
+            my $discount = $self->option('rate');
+           return (round($discount) * -1);
+          }
+    }
+  }
+
+  return 0;  
+
+}
+
+
+sub is_free_options {
+  qw( setup_fee recur_flat recur_unit_charge );
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('base_fee');
+}
+
+sub round {
+    my($number) = shift;
+    my $roundit= int($number*100 + .5);
+       return sprintf('%.2f',$roundit/100)
+}
+
+1;
diff --git a/FS/FS/part_pkg/energy_base_discount_tiers.pm b/FS/FS/part_pkg/energy_base_discount_tiers.pm
new file mode 100755 (executable)
index 0000000..62f6333
--- /dev/null
@@ -0,0 +1,160 @@
+package FS::part_pkg::energy_base_discount_tiers;
+
+use strict;
+use vars qw(@ISA %info %penalty_fee);
+use DBI;
+use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+use FS::usage_elec;
+use Date::Format;
+use Date::Parse;
+use Data::Dumper;
+
+
+@ISA = qw(FS::part_pkg::flat);
+
+tie %penalty_fee,'Tie::IxHash',
+  '0'=>0,
+  '0.05'=>0.05,
+;
+
+
+%info = (
+  'name'       => 'Energy base discount tiers',
+  'svc_elec_compatible' => 1,
+  'fields'     => 
+       {
+         'description'    => 
+             { 'name'    => 'Description printed on bill',
+               'default' => 'SPECIAL BASE CHARGE DISCOUNT FOR TIERS USAGE',
+             },
+         'rate'=>
+             { 'name'    => 'Tiers Discount Amount',
+               'default' => '0-499:0.00;500-999:3.00;1000-:7.95',
+             },
+         'penalty'        => 
+             { 'name'=>'Late fee',
+               'type' =>'select',
+               'select_options'=> \%penalty_fee,
+             },
+       },
+  'fieldorder' => [ 'description', 'rate' ],
+  'weight' => '70',
+);
+
+sub calc_recur {
+  my($self, $cust_pkg ) = @_;
+  my $date =0;
+
+  # this fee is dependent on the existence of a base elecusage package existence
+  # so let check if it exist.
+  my $custnum = $cust_pkg->custnum;
+  my $basic_engpkg_exist;
+  my $usage_svcnum;
+  my $lastdate;
+
+  foreach my $cust_pkg_tmp ( qsearch(
+                              {
+                               'table'  => 'cust_pkg',
+                               'hashref'=> { 'custnum' => $custnum },
+                               'extra_sql' => 'ORDER BY pkgnum ASC' 
+                              } )
+                           ) {
+    next if $cust_pkg_tmp->getfield('cancel');
+    # -ctran 06/09/08
+    # updated liteup
+    next if $cust_pkg_tmp->getfield('susp');
+    next if ($cust_pkg_tmp->getfield('pkgnum') == $cust_pkg->pkgnum);
+
+    my $pkgnum = $cust_pkg_tmp->getfield('pkgnum');
+
+    my $cust_svc_tmp = qsearchs('cust_svc',{'pkgnum' => $pkgnum});
+
+    #check for keyword ESIID from svc_external
+    if ($cust_svc_tmp) {
+      my $svc_external = qsearchs('svc_external',{'svcnum'=>$cust_svc_tmp->svcnum});
+
+      if ($svc_external) {
+        if (!$basic_engpkg_exist && ($svc_external->title =~ /^ESIID$/i)) {
+          $basic_engpkg_exist = 1;
+          $usage_svcnum = $cust_svc_tmp->getfield('svcnum');
+          $lastdate =$cust_pkg_tmp->last_bill ||0;
+        }
+      }
+    }
+    
+  }
+
+  # now let get the usage if a energy package exist
+  if ($basic_engpkg_exist) {
+    my  @usage_elecs=qsearch(
+                       {
+                        'table'    => 'usage_elec',
+                        'hashref'  => { 'svcnum'  => $usage_svcnum,
+    #                                    '_date'   => { 'op' => '>',
+    #                                                   'value' => $lastdate
+    #                                                 }
+                                      },
+                        'extra_sql' => 'ORDER BY _date DESC'
+                       });
+
+    
+    if(defined($usage_elecs[0])) {
+      my $usage = $usage_elecs[0]->getUsage;
+      if ($usage) {
+        my $rate = $self->option('rate');
+        foreach my $tier (split(';',$rate)) {
+          my ($range, $disc_val) = split(':',$tier);
+          my ($min,$max) = split('-',$range); 
+          #set default value
+          #$min = 0 unless defined $min; 
+         if ($min) {
+            if ($min <= $usage) {
+              if ($max) {
+                if ($usage <= $max) {
+                  return (round($disc_val) * -1);
+                }
+              }
+              else {
+                #there no max
+                return (round($disc_val) * -1);
+              }
+            }
+          }
+         else {
+            if ($max) {
+              if ($usage <= $max) {
+                return (round($disc_val) * -1);
+             }
+            }
+            else {
+              #there no max
+              return (round($disc_val) * -1);
+            }
+         }
+        }#for 
+      }
+    }
+  }
+
+  return 0;  
+
+}
+
+
+sub is_free_options {
+  qw( setup_fee recur_flat recur_unit_charge );
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('base_fee');
+}
+
+sub round {
+    my($number) = shift;
+    my $roundit= int($number*100 + .5);
+       return sprintf('%.2f',$roundit/100)
+}
+
+1;
diff --git a/FS/FS/part_pkg/residential_elec_generic.pm b/FS/FS/part_pkg/residential_elec_generic.pm
new file mode 100755 (executable)
index 0000000..b88ed64
--- /dev/null
@@ -0,0 +1,94 @@
+package FS::part_pkg::residential_elec_generic;
+
+use strict;
+use vars qw(@ISA %info %penalty_fee $DEBUG);
+use DBI;
+use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+use FS::usage_elec;
+
+@ISA = qw(FS::part_pkg::flat);
+$DEBUG = 0;
+
+tie %penalty_fee,'Tie::IxHash',
+  '0'=>0,
+  '0.05'=>0.05,
+;
+
+
+%info = (
+  'name' => 'Residential base package',
+  'svc_elec_compatible' => 1,
+  'fields' => {
+    'setup_fee' => { 'name' => 'Setup fee for this package',
+                     'default' => 0,
+                   },
+    'base_fee' => { 'name' => 'Base fee for this package',
+                      'default' => 0,
+                    },
+    'rate' => { 'name' => 'Rate for customer',
+                               'default' => 1,
+                             },
+    'rate1_discount' => { 'name'    => 'Discount rate #1 (blank=disable)',
+                          'default' => '',
+                        },
+       'penalty' => { 'name'=>'Late fee',
+                   'type' =>'select',
+                  'select_options'=> \%penalty_fee,
+        },
+  },
+  'fieldorder' => [ 'setup_fee', 'base_fee','rate', 'rate1_discount', 'penalty' ],
+ 'weight' => '70',
+);
+
+sub calc_recur {
+  my($self, $cust_pkg ) = @_;
+  my $date =0;
+  # -cal 7/5/07 added debug comment to those line that tommy use for debugging
+  #             then comment them out
+  my  $cust_svc=qsearchs('cust_svc',{'pkgnum' => $cust_pkg->pkgnum});
+  my $lastdate =$cust_pkg -> last_bill ||0;
+  warn $lastdate."\n" if $DEBUG;
+  warn $cust_svc->svcnum."\n" if $DEBUG;
+  warn $cust_pkg->pkgnum."\n" if $DEBUG;
+  my  @usage_elecs=qsearch('usage_elec',{'svcnum' => $cust_svc->svcnum,
+                                        '_date'=> { op=>'>', value=>$lastdate },
+                              'extra_sql' => 'ORDER BY _date_'});
+
+  warn "test".@usage_elecs."\n" if $DEBUG;
+  
+  if(defined($usage_elecs[0])){
+       warn "test2".$usage_elecs[0]->id."\n" if $DEBUG;
+       warn $usage_elecs[0]->getUsage."usage\n" if $DEBUG;
+       my $base=$self->option('base_fee');
+       my $rate=$self->option('rate');
+       my $sum= $base + ($usage_elecs[0]->getUsage)*$rate+$usage_elecs[0]->tdsp;
+       warn $sum."\n" if $DEBUG;
+       warn "$base * $rate = ".$base*$rate if $DEBUG;
+       return round($sum);
+       }
+  return 0;  
+  #$hours -= $self->option('recur_included_hours');
+  #$hours = 0 if $hours < 0;
+
+  #$self->option('recur_flat') + $hours * $self->option('recur_hourly_charge');
+  #return 99;
+}
+
+
+sub is_free_options {
+  qw( setup_fee recur_flat recur_unit_charge );
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('base_fee');
+}
+
+sub round {
+    my($number) = shift;
+    my $roundit= int($number*100 + .5);
+       return sprintf('%.2f',$roundit/100)
+}
+
+1;
diff --git a/FS/FS/part_pkg/residential_elec_generic_var.pm b/FS/FS/part_pkg/residential_elec_generic_var.pm
new file mode 100755 (executable)
index 0000000..584de70
--- /dev/null
@@ -0,0 +1,113 @@
+package FS::part_pkg::residential_elec_generic_var;
+
+use strict;
+use vars qw(@ISA %info %penalty_fee);
+use Date::Format;
+use Data::Dumper;
+use DBI;
+use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+use FS::usage_elec;
+
+@ISA = qw(FS::part_pkg::flat);
+
+tie %penalty_fee,'Tie::IxHash',
+  '0'=>0,
+  '0.05'=>0.05,
+;
+
+
+%info = (
+  'name' => 'Residential base package var',
+  'svc_elec_compatible' => 1,
+  'fields' => {
+    'setup_fee' => { 'name' => 'Setup fee for this package',
+                     'default' => 0,
+                   },
+    'base_fee' => { 'name' => 'Base fee for this package',
+                      'default' => 0,
+                    },
+    'rate' => { 'name' => 'Default Rate for customer',
+                          'default' => '0.12',
+                             },
+    'vrate' => { 'name' => 'Variable Rate (blank=disable)',
+                          'default' => '2008-01:0.12;2009-01:0.12',
+                             },
+    'rate1_discount' => { 'name'    => 'Discount rate #1 (blank=disable)',
+                          'default' => '',
+                        },
+       'penalty' => { 'name'=>'Late fee',
+                   'type' =>'select',
+                  'select_options'=> \%penalty_fee,
+        },
+  },
+  'fieldorder' => [ 'setup_fee', 'base_fee','rate', 'vrate', 'rate1_discount', 'penalty' ],
+ 'weight' => '70',
+);
+
+sub calc_recur {
+  my($self, $cust_pkg ) = @_;
+  my $date =0;
+  # -cal 7/5/07 added debug comment to those line that tommy use for debugging
+  #             then comment them out
+
+  # generate the variable rate hash
+  my $vrate=$self->option('vrate');
+  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=qsearchs('cust_svc',{'pkgnum' => $cust_pkg->pkgnum});
+  my $lastdate =$cust_pkg -> last_bill ||0;
+  my  @usage_elecs=qsearch('usage_elec',{'svcnum' => $cust_svc->svcnum,
+                                        '_date'=> { op=>'>', value=>$lastdate },
+                              'extra_sql' => 'ORDER BY _date_'});
+
+  if(defined($usage_elecs[0])){
+       my $base=$self->option('base_fee');
+       my $rate=$self->option('rate');
+       # usage end date
+       my $usage_enddate_year = time2str('%Y',$usage_elecs[0]->curr_date);
+       my $usage_enddate_month = time2str('%m',$usage_elecs[0]->curr_date);
+        #my $v_rate = $rate;
+       if ($vrate) {
+         # if a variable rate
+                 $rate = $var_rate{$usage_enddate_year}{$usage_enddate_month} 
+                   if (exists $var_rate{$usage_enddate_year}{$usage_enddate_month});
+       }
+
+        my $sum= $base + ($usage_elecs[0]->getUsage)*$rate+$usage_elecs[0]->tdsp;
+
+       return round($sum);
+       }
+  return 0;  
+  #$hours -= $self->option('recur_included_hours');
+  #$hours = 0 if $hours < 0;
+
+  #$self->option('recur_flat') + $hours * $self->option('recur_hourly_charge');
+  #return 99;
+}
+
+
+sub is_free_options {
+  qw( setup_fee recur_flat recur_unit_charge );
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('base_fee');
+}
+
+sub round {
+    my($number) = shift;
+    my $roundit= int($number*100 + .5);
+       return sprintf('%.2f',$roundit/100)
+}
+
+1;
diff --git a/FS/FS/svc_elec.pm b/FS/FS/svc_elec.pm
new file mode 100755 (executable)
index 0000000..c86a72c
--- /dev/null
@@ -0,0 +1,138 @@
+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;
diff --git a/FS/FS/transaction810.pm b/FS/FS/transaction810.pm
new file mode 100755 (executable)
index 0000000..569d9b8
--- /dev/null
@@ -0,0 +1,307 @@
+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('-',@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;
+
diff --git a/FS/FS/transaction867.pm b/FS/FS/transaction867.pm
new file mode 100755 (executable)
index 0000000..4b3bed0
--- /dev/null
@@ -0,0 +1,300 @@
+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('-',@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;
+
diff --git a/FS/FS/usage_elec.pm b/FS/FS/usage_elec.pm
new file mode 100755 (executable)
index 0000000..55c45de
--- /dev/null
@@ -0,0 +1,647 @@
+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;
+
diff --git a/FS/FS/usage_elec_transaction867.pm b/FS/FS/usage_elec_transaction867.pm
new file mode 100755 (executable)
index 0000000..6373eca
--- /dev/null
@@ -0,0 +1,124 @@
+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;
+
index 1b2e08d..5bcbaff 100644 (file)
@@ -528,3 +528,16 @@ FS/part_tag.pm
 t/part_tag.t
 FS/svc_CGP_Mixin.pm
 FS/svc_CGPRule_Mixin.pm
+FS/elec_general.pm
+t/elec_general.t
+FS/svc_elec.pm
+t/svc_elec.t
+FS/usage_elec.pm
+t/usage_elec.t
+FS/transaction810.pm
+t/transaction810.t
+FS/transaction867.pm
+t/transaction867.t
+FS/usage_elec_transaction867.pm
+t/usage_elec_transaction867.t
+
diff --git a/FS/t/elec_general.t b/FS/t/elec_general.t
new file mode 100755 (executable)
index 0000000..9dfb1d2
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::elec_general;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_elec.t b/FS/t/svc_elec.t
new file mode 100755 (executable)
index 0000000..e7fda34
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_elec;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/transaction810.t b/FS/t/transaction810.t
new file mode 100755 (executable)
index 0000000..98c6b88
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::transaction810;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/transaction867.t b/FS/t/transaction867.t
new file mode 100755 (executable)
index 0000000..699de3d
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::transaction867;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/usage_elec.t b/FS/t/usage_elec.t
new file mode 100755 (executable)
index 0000000..4824919
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::usage_elec;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/usage_elec_transaction867.t b/FS/t/usage_elec_transaction867.t
new file mode 100755 (executable)
index 0000000..086edff
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::usage_elec_transaction867;
+$loaded=1;
+print "ok 1\n";
diff --git a/conf/rec_latex b/conf/rec_latex
new file mode 100755 (executable)
index 0000000..d8b1bc4
--- /dev/null
@@ -0,0 +1,315 @@
+%% LyX 1.4.3-5 created this file.  For more info, see http://www.lyx.org/.
+%% Do not edit unless you really know what you are doing.
+\documentclass[english]{article}
+\usepackage{times}
+\usepackage[T1]{fontenc}
+\usepackage[latin1]{inputenc}
+\usepackage[bottom=1cm] {geometry}
+\usepackage{graphicx}
+\usepackage{multirow,colortbl}
+\geometry{verbose,letterpaper}
+\usepackage{array}
+\usepackage{calc}
+\usepackage{color}
+\thispagestyle{empty}
+\makeatletter
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% LyX specific LaTeX commands.
+%% Because html converters don't know tabularnewline
+\providecommand{\tabularnewline}{\\}
+\newcommand{\addspacefive}{&&&&\tabularnewline[-0.1in]}
+\newcommand{\addspaceseven}[1]{&&&&&&\tabularnewline[#1]}
+\newcommand{\addspacetwo}{&\tabularnewline[0.01in]}
+\usepackage{babel}
+\makeatother
+\addtolength{\hoffset}{-3.1cm} 
+%\addtolength{\voffset}{-2.75cm}               % top margin to top of header
+\setlength{\textheight}{10.9in}
+\begin{document}
+
+
+\begin{minipage}[t][0.25\totalheight]{0.30\columnwidth}%
+\vspace{0pt}
+\centering
+%\includegraphics[width=1\textwidth]{logo.eps} 
+%\includegraphics{logo.eps} 
+{{\includegraphics{[@-- $conf_dir --@]/logo}}}
+\end{minipage}
+\hspace{1.2in}
+\begin{minipage}[t][0.20\totalheight]{0.4\columnwidth}
+\vspace{0pt}
+\centering
+
+\large [@-- 
+         @array=split(/\n/, $returnaddress);
+         $help=1;
+         foreach $line(@array){
+           if($line eq ""){
+             $line ="~"
+           }
+           if($help == 1){
+             $OUT= "\\textbf{".$line."}";
+             $help=0;
+           }
+           else{
+             $OUT = $OUT."\\\\ ".$line;
+           }
+         }
+      --@]
+%\large \textbf{Addresse}\\ von ONPAC\\ Strasse 34der\\keine\\~\\ahnung
+\end{minipage}
+
+%\vspace{-0.1in}
+\vspace{-0.3in}
+\hspace{5.9in}
+\begin{minipage}[t][0.25\totalheight]{0.3\columnwidth}
+\vspace{0pt}
+\centering
+\normalsize
+Office Hours:\\
+Monday To Friday\\
+9:00 a.m - 5:00 p.m\\
+Customer Billing Support\\
+1-866-696-6722
+\end{minipage}
+\vspace{.8 in}
+
+\begin{minipage}[t][0.8\totalheight]{1\columnwidth}%
+\setlength{\arrayrulewidth}{0.8pt}
+%2.5 inch = 0.4203\textwidth
+
+\begin{tabular}{|>{\centering}p{0.1345\textwidth}|>{\centering}p{0.41\textwidth}|>{\centering}p{0.0606\textwidth}|>{\centering}p{0.2185\textwidth}|>{\centering}p{0.36\textwidth}|}
+\hline
+
+\rowcolor{black}
+\rule{0mm}{4mm}
+{\normalsize \textbf{\color{white}{ACCOUNT\#}}}&
+{\normalsize \textbf{\color{white}{CUSTOMER NAME}}}&
+{\normalsize \textbf{\color{white}{RATE}}}&
+{\normalsize \textbf{\color{white}{PHONE NUMBER}}}&
+{\normalsize \textbf{\color{white}{SERVICE ADDRESS}}}\tabularnewline[.2ex]
+\hline
+\addspacefive
+\normalsize [@-- $custnum --@] &
+\normalsize [@-- if($company){$company}
+                 else{$payname} --@]&
+\normalsize [@-- sprintf('%.3f',$rate) --@]&
+\normalsize [@-- $phone --@]&
+\normalsize [@-- $srvc_addr --@]\tabularnewline
+\hline
+\end{tabular}%
+\vspace{5mm}
+%~\newline
+%\Large{\textbf{STATEMENT SUMMARY}} \newline
+\Large{\textbf{ACCOUNT SUMMARY}} \newline
+\normalsize
+\vspace{3mm}
+\begin{tabular}{l l l}
+{{ESSID: [@--$esiid--@] }} &
+{{METER \#: [@--$meter_number--@] }} &
+{{SERVICE CODE: [@--$pkg_info--@]}}
+\end{tabular}
+%\vspace{3mm}
+%\normalsize {\textbf{ESSID: [@--$esiid--@] }} \newline
+%\normalsize {\textbf{METER \#: [@--$meter_number--@] }} \newline
+%\normalsize {\textbf{SERVICE CODE: [@--$pkg_info--@]}} \newline
+\large
+~\newline
+\vspace{3mm}
+%\begin{tabular}{|>{\centering}p{0.75in}>{\centering}p{0.75in}>{\centering}p{0.75in}>
+%{\centering}m{0.75in}|>{\centering}p{0.75in}>{\centering}p{0.75in}||>{\centering}p{0.75in}>{\centering}p{0.75in}|}
+\begin{tabular}{|>{\centering}p{0.75in}{r}>{\centering}p{0.50in}>
+{\centering}m{1.75in}|>{\centering}p{0.75in}{r}||>{\centering}p{0.75in}{r}|}
+
+\hline
+%Read Date&Usage&\# Days&Rate&Bill Date&Amount&PayDate&Amount \tabularnewline[0.2ex]
+\multicolumn{6}{|c||}{\textbf {STATEMENT}}&\multicolumn{2}{c|}{\textbf {PAYMENT}}\tabularnewline[0.2ex]
+%\hline
+\multicolumn{1}{|c}{Read Date}&\multicolumn{1}{c}{Usage}&\multicolumn{1}{c}{\# Days}
+&\multicolumn{1}{c}{Note}&\multicolumn{1}{c}{Bill Date}
+&\multicolumn{1}{c||}{Amount}&\multicolumn{1}{c}{PayDate}
+&\multicolumn{1}{c|}{Amount}\tabularnewline[0.2ex]
+\hline
+\normalsize
+[@-- 
+  use Date::Format;
+  $OUT='';
+  my $billlength=@total_bills;
+  my $paylength=@total_payments;
+  my $detaillength=@total_details;
+       
+  my $max =$billlength;
+  $max=$paylength if($paylength>$max);
+
+  for($i=0;$i<$max;$i=$i+1){
+    if($i <$detaillength){
+
+      if(@total_details[$i]->energy_usage>0){
+        $OUT.= '{\small '.time2str('%D',@total_details[$i]->curr_date)."}&";
+      }
+      else{
+        $OUT.="&";
+      }
+
+      $OUT.= '{\small '.sprintf('%.0f',@total_details[$i]->energy_usage)."}&";
+      $OUT.= '{\small '.@total_details[$i]->number_of_days."}&";
+      if (@total_details[$i]->curr_date) {
+        $OUT.= '&';
+      }
+      else {
+        $OUT.= '&';
+        #if (@total_details[$i]->one_time_description) {
+        #  $OUT.= '{\small '.lc (sprintf('%20s',@total_details[$i]->one_time_description))."}&";
+        #}
+        #else {
+        #  $OUT.= '&';
+        #}
+      }
+    }
+    else{
+      $OUT.="&&&";
+    }
+
+    if($i <$billlength){
+      $OUT.= '{\small '.time2str('%D',@total_bills[$i]->_date)."}&";
+      $OUT.= '{\small '.@total_bills[$i]->charged."}&";
+    }
+    else{
+      $OUT.="&&&";
+    }
+
+    if($i <$paylength){
+      $OUT.= '{\small '.time2str('%D',@total_payments[$i]->_date)."}&";
+      $OUT.= '{\small '.@total_payments[$i]->paid .'}';
+    }
+    else{
+      $OUT.="&";
+    }
+
+    $OUT.="\\small \\tabularnewline\n";
+  }
+
+  for(my $i=$max;$i<20;$i=$i+1){
+    $OUT.="&&&&&&&\\tabularnewline\n";
+  }
+       
+--@]
+       \hline
+       \end{tabular}
+\vspace{3mm}
+\large{\textbf{CURRENT BALANCE: [@-- $actual_balance --@]}}
+~\newline
+\normalsize
+\begin{tabular}{>{\centering}p{8in}}
+\hspace{3in}{\small{Please detach and return your payment payable to ONPAC Energy}}\tabularnewline[0.2ex]
+\end{tabular}
+%\begin{tabular}{|>{\raggedright}p{6.315in}|>{\centering}p{1.08in}|}
+%\vspace{-0.5cm}
+%\vspace{-0.5cm}
+%%\addspacetw
+%\textbf{ENERGY BASE}&
+%\textbf{4.95}\tabularnewline
+%\end{tabular}
+%\vspace{0.2in} #the delivery address
+\vspace{.4in}
+%\begin{tabular}{>{\centering}p{3.955in}c}
+\begin{tabular}{>{\centering}p{3.455in}c}
+
+
+\begin{tabular}{c}
+
+%\tabularnewline #move everything down
+\tabularnewline[8ex]
+\large \textbf{OnPAC Energy}\\
+\tabularnewline[-2ex]
+\textbf{ P.O. Box 831787, Richardson TX 75083-1787}\\
+\textbf{REP PUCT Number 10077}
+\tabularnewline[4ex]
+
+\setlength{\fboxrule}{1pt}
+\fbox{\fcolorbox{black}{black}{\parbox[c][1\totalheight]{0.4\columnwidth}{
+[@-- 
+    # print PAST DUE if ballance is greater than zero
+    $OUT = '';
+    if ($actual_balance > 0 && !ignore_due_date) {
+      $OUT = '\LARGE \centering {\textbf{\textcolor{white}{ACCOUNT \linebreak PAST DUE}}}';
+    }
+    else {
+      $OUT = '\LARGE \centering {\textbf{\textcolor{white}{ACCOUNT \linebreak SUMMARY}}}';
+    }
+ --@]
+}}}%
+\tabularnewline
+
+\end{tabular}&
+%\begin{tabular}{|>{\centering}p{1.3in}|>{\centering}p{0.78in}|>{\centering}p{1.05in}|}
+\begin{tabular}{|>{\centering}p{1.3in}|>{\centering}p{0.78in}|>{\centering}p{1.05in}|}
+\hline 
+&&\tabularnewline[-2ex]
+%\rowcolor{black}
+ACCOUNT NUMBER&
+DUE DATE&
+BALANCE \tabularnewline
+\hline
+\hline 
+&&\tabularnewline[-2ex]
+\normalsize \textbf{[@--$custnum--@]}&
+%\normalsize \textbf{[@--$due_date--@]}&
+[@--
+    # don't print the due date if balance is zero
+    $OUT = '';
+    if ($actual_balance > 0) {
+      $OUT = '\normalsize \textbf{' . $due_date . '}&';
+    }
+    else {
+      $OUT ='&';
+    }
+ --@]
+\normalsize \textbf{[@--$actual_balance--@]}\tabularnewline[0.3ex]
+\hline 
+&&\tabularnewline[-2ex]
+Billing Date&
+Rate Code&
+Meter \#\tabularnewline
+\hline 
+&&\tabularnewline[-2ex]
+%\textbf{[@--$date--@]}&
+[@--
+    # don't print the billing date if balance is zero
+    $OUT = '';
+    if ($actual_balance > 0) {
+      $OUT = '\textbf{'.$date.'}&';
+    }
+    else {
+      $OUT ='&';
+    }
+ --@]
+\textbf{[@--sprintf('%.3f',$rate)--@]}&
+\textbf{[@--$meter_number--@]}\tabularnewline
+\hline
+
+\end{tabular}\tabularnewline
+\end{tabular}
+
+\begin{tabular}{>{\raggedright}p{4.17in}l}
+%\tabularnewline[-0.08in]
+\tabularnewline[-.10in]
+\hspace{0.4in}\large{\textbf{[@-- $company --@]}}&
+\tabularnewline
+\hspace{0.4in}\large{\textbf{[@-- $payname --@]}}&
+\tabularnewline
+\hspace{0.4in}\large{\textbf{[@-- $address1 --@]}}&
+\hspace{0.4in} \sffamily{\large{*[@--$custnum--@]*}}\tabularnewline%[0.4ex]
+\hspace{0.4in}\large{\textbf{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}}&
+%\hspace{0.6in}\large{\textbf{[@-- $address1 --@]}}&
+%\hspace{0.6in} \sffamily{\large{*[@--$custnum--@]*}}\tabularnewline%[0.4ex]
+%\hspace{0.6in}\large{\textbf{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}}&
+\tabularnewline
+
+
+\end{tabular}
+
+\end{minipage}%
+
+
+\end{document}
diff --git a/httemplate/edit/process/usage_elec_manual_input.cgi b/httemplate/edit/process/usage_elec_manual_input.cgi
new file mode 100755 (executable)
index 0000000..1a62c39
--- /dev/null
@@ -0,0 +1,225 @@
+%
+%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+%my $svcnum =$1;
+%$svcnum = $cgi->param('svcnum');
+svcnum = <% $svcnum %><br>
+%
+%my $old = qsearchs('svc_external',{'svcnum'=>$svcnum}) if $svcnum;
+%
+%
+%### this is the field name for usage_elec with the exception of
+%### prev_date, curr_date, _date
+%my @field_name = qw / prev_read curr_read tdsp
+%                      meter_multiplier total_usage measured_demand
+%                      billed_demand svcnum meter_number /;
+%my ($prev_date, $curr_date, $_date);
+%
+%my %usage_hash = (
+%                   map {
+%                        ($_, scalar($cgi->param($_)))
+%                       } ( @field_name )
+%                 );
+%
+% my $error = '';
+%
+% # Some general rules regarding the data
+% # prev_date, curr_date - 
+% #             8 digit in format of yyyymmdd (y-year m-month d-date)
+% # prev_read, curr_read - positive interger. curr_read > prev_read
+% # tdsp - an dollar amount w/wo cent
+% # meter_multiplier - positive integer
+% # total_usage - 
+% #         should equal (total_usage = (prev_read-curr_read) * meter_multiplier
+% #         unless meter multiplier ignore value is set
+% # measured_demand - positive integer
+% # billed_demand - positive integer
+% # meter_number - alpha numeric value
+%
+% # prev_date, curr_date - 
+% #             8 digit in format of yyyymmdd (y-year m-month d-date)
+% my ($pd, $cd) = ($cgi->param('prev_date'),$cgi->param('curr_date'));
+% if ( $pd =~ /^(\d{4})(\d{2})(\d{2})$/ ) {
+%   my ($y,$m,$d) = ($1,$2,$3);
+%   if ($m < 01 || $m > 12 || $d < 01 || $d > 31) {
+%     $error = "error: previous date '$pd' must follow the rule"
+%             ." of being 8 digit in format of yyyymmdd (y-year m-month d-date)";
+%     $cgi->param('error', $error);
+%     print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+%   }
+% }
+% else {
+%   $error = "error: previous date '$pd' must follow the rule"
+%           ." of being 8 digit in format of yyyymmdd (y-year m-month d-date)";
+%   $cgi->param('error', $error);
+%   print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+% }
+% if ( ($cd =~ /^(\d{4})(\d{2})(\d{2})$/) ) {
+%   my ($y,$m,$d) = ($1,$2,$3);
+%   if ($m < 01 || $m > 12 || $d < 01 || $d > 31) {
+%     $error = "error: previous date '$cd' must follow the rule"
+%             ." of being 8 digit in format of yyyymmdd (y-year m-month d-date)";
+%     $cgi->param('error', $error);
+%     print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+%   }
+% }
+% else {
+%   $error = "error: previous date '$cd' must follow the rule"
+%           ." of being 8 digit in format of yyyymmdd (y-year m-month d-date)";
+%   $cgi->param('error', $error);
+%   print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+% }
+%
+%
+% my $multiplier_ignore_flag = $cgi->param('ignore_meter_multiplier');
+%
+% # check prev_read and curr_read
+% my ($pr, $cr) = ($usage_hash{'prev_read'},$usage_hash{'curr_read'});
+% if ($pr =~ /^\d+$/ && $cr =~ /^\d+$/) {
+%   # prev and curr are integer
+%   if ( ($pr > $cr) && (!$multiplier_ignore_flag) ) {
+%     # prev > current .. this is not possible unless meter change
+%     $error = "error: previous reading '$pr' is greater than current reading"
+%             ." '$cr'\n";
+%     $cgi->param('error', $error);
+%     print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+%   }
+% }
+% else {
+%  $error = "error: previous reading '$pr' or current reading '$cr'"
+%          ." need to follow the simple rule of being a positive integer.\n";
+%  $cgi->param('error', $error);
+%  print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+% }
+%
+% # tdsp - an dollar amount w/wo cent
+% my $tdsp = $usage_hash{'tdsp'};
+% if ( $tdsp !~ /^(\d+|\d+\.\d{2})$/ ) {
+%   $error = "error: tdsp '$tdsp' must follow the rule<br>"
+%          ." of being a dollar amount w/wo cent value.<br>";
+%   $cgi->param('error', $error);
+%   print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+% }
+%
+% # meter_multiplier - positive integer
+% my $mm = $usage_hash{'meter_multiplier'};
+% #if ( ($mm < 0) || ($mm !~ /^\d+$/) ) {
+% if ( ($mm < 0) || ($mm !~ /^\d+\.{0,1}\d*$/) ) {
+%   $error = "error: meter multiplier '$mm' must follow the rule<br>"
+%          ." of being a positive integer.<br>";
+%   $cgi->param('error', $error);
+%   print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+% }
+%
+% # total_usage - 
+% #         should equal (total_usage = (curr_read-prev_read) * meter_multiplier
+% #         unless meter multiplier ignore value is set
+% my $input_tu = $usage_hash{'total_usage'};
+% my $tu = ($cr-$pr)*$mm;
+% if ( ( ($tu != $input_tu) && (! $multiplier_ignore_flag)) ) {
+%  # total usage didn't equal formula and there were no ignore set
+%  $error = "error: total usage '$input_tu' '$tu' must follow the formula<br>"
+%          ." total_usage = (current_reading - previous_reading) * meter_multiplier<br>"
+%          ." unless the meter multiplier ignore flag '$multiplier_ignore_flag' is set.<br>";
+%  $cgi->param('error', $error);
+%  print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+% }
+%
+% # measured_demand - positive integer
+% my $md = $usage_hash{'measured_demand'};
+% if ( ($md < 0) || ($md !~ /^\d+$/) ) {
+%  $error = "error: measured demand '$md' must follow the rule<br>"
+%          ." of being a positive integer.<br>";
+%  $cgi->param('error', $error);
+%  print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+% }
+%
+% # billed_demand - positive integer
+% my $bd = $usage_hash{'billed_demand'};
+% if ( ($bd < 0) || ($bd !~ /^\d+$/) ) {
+%   $error = "error: billed demand '$bd' must follow the rule<br>"
+%          ." of being a positive integer.<br>";
+%   $cgi->param('error', $error);
+%   print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+% }
+%
+% # meter_number - alpha numeric value
+% my $mn = $usage_hash{'meter_number'};
+% if ( $mn !~ /^[a-z0-9]+$/i ) {
+%  $error = "error: meter number '$mn' must follow the rule<br>"
+%          ." of being a alpha numeric value.<br>";
+%  $cgi->param('error', $error);
+%  print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+% }
+%
+%
+%# convert the field date to it strtime
+%$prev_date = FS::usage_elec::to_usage_elec_time($cgi->param('prev_date'));
+%unless ($prev_date) {
+%  $error = "error: unable to convert prev_date ".$cgi->param('prev_date')
+%          ." to a usable time for usage_elec\n";
+%  $cgi->param('error', $error);
+%  print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+%}
+%
+%$curr_date = FS::usage_elec::to_usage_elec_time($cgi->param('curr_date'));
+%unless ($curr_date) {
+%  $error = "error: unable to convert curr_date ".$cgi->param('curr_date')
+%          ." to a usable time for usage_elec\n";
+%  $cgi->param('error', $error);
+%  print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+%}
+%
+%$_date = time;
+%
+%$usage_hash{prev_date} = $prev_date;
+%$usage_hash{curr_date} = $curr_date;
+%$usage_hash{_date} = $_date;
+%
+%my $new = new FS::usage_elec( \%usage_hash );
+%
+%if ( $svcnum ) {
+%  $error = $new->insert_usage;
+%} else {
+%  $error = "error: can't insert data into usage_elec table without a "
+%          ."service number\n"
+%} 
+%
+%if ($error) {
+%  # handle can't insert data into usage_elec
+%  $cgi->param('error', $error);
+%  print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+%} else {
+%  # look like the data has been inserted into usage_elec successfully
+%  # now let generate the bill
+%  $error = FS::usage_elec::billing_call($new);
+%  if ($error) {
+%    # handle can't execute billing
+%    $error = "error: Execution of billing failed.\n"
+%            ."$error";
+%    # delete the usage_elec that was just entered because billing failed
+%    $new->delete; 
+%    $cgi->param('error', $error);
+%    print $cgi->redirect(popurl(2). "usage_elec_manual_input.cgi?"
+%               . $cgi->query_string );
+%  }
+%
+%  print $cgi->redirect(popurl(3). "view/svc_external.cgi?$svcnum");
+%}
+%
+%
+
diff --git a/httemplate/edit/usage_elec_manual_input.cgi b/httemplate/edit/usage_elec_manual_input.cgi
new file mode 100755 (executable)
index 0000000..7f9d9fa
--- /dev/null
@@ -0,0 +1,213 @@
+%my $debug=0; # toggle debug
+%my( $svcnum,  $pkgnum, $svcpart, $part_svc, $svc_external );
+%my @field_descriptions = ( 'prev date', 'curr date', 'prev reading',
+%                           'curr reading', 'tdsp', 'meter mult',
+%                           'total usage', 'measured demand', 'billed demand',
+%                           'svcnum', 'entry date', 'meter number' );
+%my @field_name = qw / prev_date curr_date prev_read curr_read tdsp 
+%                      meter_multiplier total_usage measured_demand
+%                      billed_demand svcnum _date meter_number /;
+%my $date_exception = '(prev_date|curr_date|_date)';
+%
+%if ( $cgi->param('error') ) {
+%  ### handle error call
+%  $svcnum = $cgi->param('svcnum');
+%}
+%else {
+%
+%  my($query) = $cgi->keywords;
+%  $query =~ /^(\d+)$/ or die "unparsable svcnum";
+%  $svcnum=$1;
+%
+%}
+%
+%# this is sample data for print in case no previous record of usage_elec
+%my @sample_data = ( '20070201', '20070228', '10000', '100100', '76.50',
+%                    '5', '500', '179', '220', "$svcnum", 'NA', '030234972LM');
+%
+%### this is where i start
+%### 
+%### let gather all the info from usage_elec for the particular 'svcnum'
+%###
+%my $p1 = popurl(1);
+%
+%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+%      "</FONT>"
+%  if $cgi->param('error');
+%
+%print qq!<FORM ACTION="${p1}process/usage_elec_manual_input.cgi" METHOD=POST>!;
+%
+%# print header
+%print header("Manually Adding Record to usage_elec Table", '');
+%
+%#display
+%#
+%#
+<TABLE BORDER=1>
+% 
+% # -ctran 04/10/08
+% # change getting previous 10 record to 13 so we can see at least 1 year 
+% # worth of transaction
+% # get the previous 13 usage_elec items
+% my @usage_obj = FS::usage_elec::query_usage($svcnum, 13); 
+%
+% # print the heading
+% print "<TR bgcolor=#88b2ce class='maintitle'>"
+%      . join("\n", map("<TH>" . $_ . "</TH>", @field_descriptions))
+%      . "</TR>\n";
+%
+% if (@usage_obj) {
+%   foreach my $usage (@usage_obj) {
+%     # fill @usage_ele with data order by @field_name
+%     my @usage_ele = ();
+%     foreach my $field (@field_name) {
+%       if ( $field =~ /$date_exception/ ) {
+%         # exception handling of converting time to string
+%         push(@usage_ele,time2str("%Y%m%d",$usage->$field));
+%       }
+%       else {
+%#debug: field= <% $field %> = <% $usage->$field %><BR>
+%         push(@usage_ele, $usage->$field); 
+%       }
+%     }
+%
+%     print "<TR bgcolor=#e8e8ea class='mainbody'>" 
+%          . join("\n", map("<TD>" . $_ . "</TD>", @usage_ele))
+%          . "</TR>\n";
+%   } 
+% }
+%
+% ###
+% ### gathering pre-filled information
+% ###
+%
+% my ($h_prev_date, $h_prev_read, $h_tdsp, $h_meter_multiplier,
+%     $h_measured_demand, $h_billed_demand, $h_svcnum, $h_meter_number);
+%
+% if (@usage_obj) {
+%  # fill in all the history data
+%  my $lindex = $#usage_obj;
+%  $h_prev_date = time2str("%Y%m%d",$usage_obj[$lindex]->curr_date);
+%  $h_prev_read = $usage_obj[$lindex]->curr_read;
+%  $h_tdsp = $usage_obj[$lindex]->tdsp;
+%  $h_meter_multiplier = $usage_obj[$lindex]->meter_multiplier;
+%  $h_measured_demand = $usage_obj[$lindex]->measured_demand;
+%  $h_billed_demand = $usage_obj[$lindex]->billed_demand;
+%  $h_svcnum = $usage_obj[$lindex]->svcnum;
+%  $h_meter_number = $usage_obj[$lindex]->meter_number;
+% }
+% 
+% # this hash store info to configure the table with text box for input
+% # size - [int] how big textbox
+% # value - [alpha numeric] default value of the text box
+% # extra - [alpha numeric] other option for text box.  I.E. READONLY
+% #         mean the text box is a readonly
+% my %field_info = (
+%                   prev_date => {
+%                                  'size'     => '8', 
+%                                  'value'    => $h_prev_date, 
+%                                },
+%                   curr_date => { 'size'     => '8' },
+%                   prev_read => {
+%                                  'size'     => '8', 
+%                                  'value'    => $h_prev_read, 
+%                                },
+%                   curr_read => { 'size'     => '8' },
+%                   tdsp      => {
+%                                  'size'     => '8', 
+%                                  'value'    => $h_tdsp,
+%                                },
+%                   meter_multiplier => {
+%                                  'size'  => '4', 
+%                                  'value' => $h_meter_multiplier,
+%                                       },
+%                   total_usage => { 'size'     => '6' },
+%                   measured_demand => {
+%                                  'size'  => '4', 
+%                                  'value' => $h_measured_demand,
+%                                      },
+%                   billed_demand => {
+%                                  'size'  => '4', 
+%                                  'value' => $h_billed_demand,
+%                                    },
+%                   svcnum => {
+%                               'size'     => '6', 
+%                               'value'    => $svcnum,
+%                               'extra'    => 'READONLY'
+%                             },
+%                   _date => {
+%                              'size'     => '8', 
+%                              'value'    => 'N/A',
+%                              'extra'    => 'READONLY'
+%                             },
+%                   meter_number => {
+%                              'size'     => '14', 
+%                              'value'    => $h_meter_number,
+%                                   },
+%                 );
+%
+%
+% # input box for entry
+% print qq !<TR bgcolor=#e8e8ea class='mainbody'>!; 
+% my $input_style = 'STYLE="color:#000000; background-color: #FFFFCC;"';
+% foreach my $field (@field_name) {
+%   my $txt = '';
+%   $txt .= ' SIZE=' . $field_info{$field}->{'size'} 
+%                                   if (exists($field_info{$field}->{'size'}));
+%   $txt .= ' VALUE="' . $field_info{$field}->{'value'} . '"' 
+%                                   if (exists($field_info{$field}->{'value'}));
+%   $txt .= ' ' . $field_info{$field}->{'extra'} 
+%                                   if (exists($field_info{$field}->{'extra'}));
+%   if ($field eq 'meter_multiplier') {
+%     print qq !
+%               <TD>
+%                <TABLE>
+%                 <TD>
+%                   <INPUT TYPE="text" $input_style NAME="$field" $txt>
+%                 </TD>
+%                 <TD>
+%                   <INPUT TYPE="checkbox" NAME="ignore_meter_multiplier">Ignore<P>
+%                 </TD>
+%                </TABLE>
+%               </TD>
+%              !;
+%   }
+%   else {
+%     print qq !
+%               <TD>
+%               <INPUT TYPE="text" $input_style NAME="$field" $txt>
+%               </TD>
+%              !;
+%  }
+% } 
+% print "</TR>\n";
+% 
+
+</TABLE><BR>
+%print "<BR>measured demand = ",$h_measured_demand,"\n<BR>" if ($debug);
+%
+<INPUT TYPE="submit" VALUE="Submit">
+<INPUT TYPE="Reset" VALUE="Clear">
+<INPUT TYPE=BUTTON OnClick="$cgi->redirect(popurl(2)."view/svc_external.cgi?$svcnum")"
+       VALUE="Cancel">
+%
+% print qq !
+%   <br><br>
+%   prev_date, curr_date -
+%               8 digit in format of yyyymmdd (y-year m-month d-date)<br>
+%   prev_read, curr_read - positive interger. Also, curr_read > prev_read
+%                          Unless meter multiplier ignore value is set.  In
+%                          this case, this condition will be ignore.<br>
+%   tdsp - an dollar amount w/wo cent<br>
+%   meter_multiplier - positive integer<br>
+%   total_usage -
+%           should equal (total_usage = (prev_read-curr_read) * meter_multiplier)
+%           unless meter multiplier ignore value is set<br>
+%   measured_demand - positive integer<br>
+%   billed_demand - positive integer<br>
+% !;
+%
+    </FORM>
+  </BODY>
+</HTML>
+
index a5bcdeb..aa067e6 100644 (file)
@@ -107,6 +107,7 @@ tie my %report_invoices_open, 'Tie::IxHash',
   'All open invoices' => [ $fsurl.'search/cust_bill.html?OPEN_date', 'All invoices with an unpaid balance' ],
   '15 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN15_date', 'Invoices 15 days or older with an unpaid balance' ],
   '30 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN30_date', 'Invoices 30 days or older with an unpaid balance' ],
+  '45 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN45_date', 'Invoices 45 days or older with an unpaid balance' ],
   '60 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN60_date', 'Invoices 60 days or older with an unpaid balance' ],
   '90 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN90_date', 'Invoices 90 days or older with an unpaid balance' ],
   '120 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN120_date', 'Invoices 120 days or older with an unpaid balance' ],
@@ -317,13 +318,42 @@ $report_menu{'SQL Query'}  = [ $fsurl.'search/report_sql.html', 'SQL Query' ]
 
 tie my %tools_importing, 'Tie::IxHash',
   'Customers' => [ $fsurl.'misc/cust_main-import.cgi', '' ],
-  'Customer comments from CSV file' => [ $fsurl.'misc/cust_main_note-import.html', '' ],
-  'One-time charges from CSV file' => [ $fsurl.'misc/cust_main-import_charges.cgi', '' ],
-  'Payments from CSV file' => [ $fsurl.'misc/cust_pay-import.cgi', '' ],
-  'Phone numbers (DIDs)' => [ $fsurl.'misc/phone_avail-import.html', '' ],
-  'Call Detail Records (CDRs)' => [ $fsurl.'misc/cdr-import.html', '' ],
-#  'Import call rates and regions' => [ $fsurl.'misc/rate-import.html', '' ],
 ;
+
+$tools_importing{'Old OnPAC customers'} = #dubious name
+  [ $fsurl.'misc/cust_main-import-oldonp.cgi', '' ]
+  if $conf->exists('svc_elec_features');
+
+$tools_importing{'Import Qualified LITEUP Customers'} =
+  [ $fsurl.'misc/qualified_liteup_customers.cgi', '' ]
+  if $conf->exists('svc_elec_features');
+
+$tools_importing{'Customer comments from CSV file'} =
+  [ $fsurl.'misc/cust_main_note-import.html', '' ];
+
+$tools_importing{'Import 810 transaction data'} =
+  [ $fsurl.'misc/transaction810-import.cgi', '' ]
+  if $conf->exists('svc_elec_features');
+
+$tools_importing{'Import OnPAC EDI data'} =
+  [ $fsurl.'misc/cust_edi_data-onp.cgi', '' ]
+  if $conf->exists('svc_elec_features');
+
+$tools_importing{'One-time charges from CSV file'} =
+  [ $fsurl.'misc/cust_main-import_charges.cgi', '' ];
+
+$tools_importing{'Payments from CSV file'} =
+  [ $fsurl.'misc/cust_pay-import.cgi', '' ];
+
+$tools_importing{'Phone numbers (DIDs)'} =
+  [ $fsurl.'misc/phone_avail-import.html', '' ];
+
+$tools_importing{'Call Detail Records (CDRs)'} =
+  [ $fsurl.'misc/cdr-import.html', '' ];
+
+#$tools_importing{'Import call rates and regions'} =
+#  [ $fsurl.'misc/rate-import.html', '' ];
+
 if ( $conf->exists('enable_taxproducts') ) {
   if ( $conf->exists('taxdatadirectdownload') ) {
       $tools_importing{'Tax rates from vendor site'} =
index 5b550db..a33ffb5 100644 (file)
 %  my %saw = ();
 %  my @custnums = grep { !$saw{$_}++ } map $_->[0], @{ $sth->fetchall_arrayref };
 %
-%  @custnums = splice(@custnums, 0, 10);
+%  my $curuser = $FS::CurrentUser::CurrentUser;
+%  my $number_of_customers =
+%    $curuser->option('dashboard_customer_history_length') || 10;
+%  @custnums = splice(@custnums, 0, $number_of_customers);
 %
 %  if ( @custnums ) {
 
diff --git a/httemplate/misc/cust_edi_data-onp.cgi b/httemplate/misc/cust_edi_data-onp.cgi
new file mode 100755 (executable)
index 0000000..985637b
--- /dev/null
@@ -0,0 +1,57 @@
+<% include("/elements/header.html",'Import EDI Info') %>
+
+<FORM ACTION="process/cust_edi_data-onp.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+
+Import a CSV file containing customer records.
+<BR><BR>
+
+<!-- Simple file format is CSV, with the following field order: <i>cust_pkg.setup, dayphone, first, last, address1, address2, city, state, zip, comments</i>
+<BR><BR> -->
+
+<!--
+Extended file format is CSV, with the following field order: <i>custnum<%$req%>, last<%$req%>, first<%$req%>, address1<%$req%>, address2, city state<%$req%>, zip<%$req%>, ss, daytime ph#, nightime ph#, service_lastname<%$req%>, service_firstname<%$req%>, service_address1<%$req%>, service_address2, service_city service_state <%$req%>, service_zip<%$req%>, newcustdate<%$req%></i> -->
+<BR><BR>
+
+<%$req%> Required fields
+<BR><BR>
+
+<% &ntable("#cccccc") %>
+
+<% include('/elements/tr-select-agent.html', '', #$agentnum,
+              'label'       => "<B>Agent</B>",
+              'empty_label' => 'Select agent',
+           )
+%>
+
+<TR>
+  <TH ALIGN="right">Format</TH>
+  <TD>
+    <SELECT NAME="format">
+<!--      <OPTION VALUE="simple">Simple -->
+      <OPTION VALUE="extended" SELECTED>Extended
+    </SELECT>
+  </TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">CSV filename</TH>
+  <TD><INPUT TYPE="file" NAME="csvfile"></TD>
+</TR>
+% #include('/elements/tr-select-part_referral.html')
+%
+
+
+<!--
+-->
+
+</TABLE>
+<BR><BR>
+
+<INPUT TYPE="submit" VALUE="Import">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%once>
+my $req = qq!<font color="#ff0000">*</font>!;
+</%once>
diff --git a/httemplate/misc/cust_main-import-oldonp.cgi b/httemplate/misc/cust_main-import-oldonp.cgi
new file mode 100755 (executable)
index 0000000..1409e0a
--- /dev/null
@@ -0,0 +1,68 @@
+<% include("/elements/header.html",'Batch Customer Import') %>
+
+<FORM ACTION="process/cust_main-import-oldonp.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+
+Import a CSV file containing customer records.
+<BR><BR>
+
+<!-- Simple file format is CSV, with the following field order: <i>cust_pkg.setup, dayphone, first, last, address1, address2, city, state, zip, comments</i>
+<BR><BR> -->
+
+Extended file format is CSV, with the following field order: <i>custnum<%$req%>, last<%$req%>, first<%$req%>, address1<%$req%>, address2, city state<%$req%>, zip<%$req%>, ss, daytime ph#, nightime ph#, service_lastname<%$req%>, service_firstname<%$req%>, service_address1<%$req%>, service_address2, service_city service_state <%$req%>, service_zip<%$req%>, newcustdate<%$req%></i>
+<BR><BR>
+
+<%$req%> Required fields
+<BR><BR>
+
+<% &ntable("#cccccc") %>
+
+<% include('/elements/tr-select-agent.html', '', #$agentnum,
+              'label'       => "<B>Agent</B>",
+              'empty_label' => 'Select agent',
+           )
+%>
+
+<TR>
+  <TH ALIGN="right">Format</TH>
+  <TD>
+    <SELECT NAME="format">
+<!--      <OPTION VALUE="simple">Simple -->
+      <OPTION VALUE="extended" SELECTED>Extended
+    </SELECT>
+  </TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">CSV filename</TH>
+  <TD><INPUT TYPE="file" NAME="csvfile"></TD>
+</TR>
+% #include('/elements/tr-select-part_referral.html')
+%
+
+
+<!--
+<TR>
+  <TH>First package</TH>
+  <TD>
+    <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION>
+% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { 
+
+       <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION>
+% } 
+
+    </SELECT>
+  </TD>
+</TR>
+-->
+
+</TABLE>
+<BR><BR>
+
+<INPUT TYPE="submit" VALUE="Import">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%once>
+my $req = qq!<font color="#ff0000">*</font>!;
+</%once>
index 813b560..f587bc2 100644 (file)
       <INPUT TYPE="hidden" NAME="paystate" VALUE="<% $paystate %>">
 %   }
 
-%   if ( $conf->exists('show_ss') ) {
+%   if ( $conf->exists('show_ss') && !$conf->exists('svc_elec_features') ) {
       <TR>
         <TD ALIGN="right">
           Account&nbsp;holder<BR>
diff --git a/httemplate/misc/process/cust_edi_data-onp.cgi b/httemplate/misc/process/cust_edi_data-onp.cgi
new file mode 100755 (executable)
index 0000000..4295a8d
--- /dev/null
@@ -0,0 +1,182 @@
+%
+%
+%  my $fh = $cgi->upload('csvfile');
+%  #warn $cgi;
+%  #warn $fh;
+%
+%  my $error = defined($fh)
+%    ? FS::cust_main::batch_edidata_onp( {
+%        filehandle => $fh,
+%        agentnum   => scalar($cgi->param('agentnum')),
+%        refnum     => scalar($cgi->param('refnum')),
+%        pkgpart    => scalar($cgi->param('pkgpart')),
+%        #'fields'    => [qw( cust_pkg.setup dayphone first last address1 address2
+%        #                   city state zip comments                          )],
+%        'format'   => scalar($cgi->param('format')),
+%      } )
+%    : 'No file';
+%
+%  if ( $error =~ /ERROR/) {
+%    
+
+    <!-- mason kludge -->
+%
+%    eidiot($error);
+%#    $cgi->param('error', $error);
+%#    print $cgi->redirect( "${p}cust_main-import.cgi
+%  } else {
+%    
+%# OK let define the heading
+%
+%# general customer info
+%my @field_descriptions1 = ( '810/867 usage match','F. Name', 'L. Name', 'Cust. Num',
+%                            'SVC Num', 'Balance', 'Last Billed','Last Read #', '');
+%
+%# info collected from 810 & 867 to be entered into usage_elec
+%my @field_descriptions2 = ( 'Start Date',
+%                            'End Date', 'Prev Read', 'Curr Read', 'TDSP',
+%                            'Meter Mulx', '867 Usage', 'Measure Demand', 'Billed Demand',
+%                            '','','','');
+%
+%# info from 810
+%my @field_descriptions3 = ( 'Invc #', '', 'Trans #/867 ref#', 'ESIID', '', '',
+%                            'TDSP','', 'Due Date', '', 'Received Date', '810 usage', '',
+%                            'Start Date', 'End Date', 
+%                            '', '', '', '', '??', 'Billed Demand', 'Measured Demand',
+%                            '','','','','','');
+%
+%# info from 867
+%my @field_descriptions4 = ( 'Prev_Read', 'Curr Read', 'Multiplier', 
+%                            'Total Usage','');
+%
+%# info from excel formula
+%my @field_descriptions5 = ( 'Total Usage Different 810&867' );
+%
+%my @p_fields = ( 'p_prev_date', 'p_curr_date', 'p_prev_reading', 
+%                 'p_curr_reading', 'p_tdsp', 'p_meter_mult', 'p_total_usage', 
+%                 'p_measured_demand', 'p_billed_demand', 'p_svcnum',
+%                 'p_first', 'p_last', 'p_balance', 'p_last_billed');
+%#                 'p_entry_date', 'p_meter_number');
+%my @p_fields_associates_index = ( 9, 10, 11,
+%                                  12, 28, 14, 15,
+%                                  16, 17, 4,
+%                                  1, 2, 5, 6); 
+%
+%#my $description = join(',',@field_descriptions);
+%
+%my @field_name = qw / prev_date curr_date prev_read curr_read tdsp
+%                      meter_multiplier total_usage measured_demand
+%                      billed_demand svcnum _date meter_number /;
+%my $date_exception = '(prev_date|curr_date|_date)';
+%my $p1 = popurl(0);
+
+%  my @usage_data = split /\n/,$error;
+
+%  #my @items = split /\n/,$error;
+%  my (@usage_cvs, @table_item);
+%  foreach my $cust_usage (@usage_data) {
+%    my @usage_ele = split ',',$cust_usage;
+%    my $svc_num = $usage_ele[4];
+%    my $cust_last_name = $usage_ele[2];
+%    my $cust_num = $usage_ele[3];
+%
+%    # first thing first, let add the last reading from usage elec into table
+%    # get the previous 1 usage_elec items
+%    my @usage_obj = FS::usage_elec::query_usage($svc_num, 1);
+%    my $usage = pop @usage_obj;
+%    $usage_ele[7] = $usage->curr_read if $usage; #only if usage exist
+%
+%    my $pass_str = '';
+%    my $i=0;
+%    foreach my $p_field (@p_fields) {
+%      my $ele_index = $p_fields_associates_index[$i];
+%      $i++;
+%      if ($pass_str) {
+%        $pass_str .= "\&${p_field}=" . $usage_ele[$ele_index];
+%      }
+%      else {
+%        $pass_str = "${p_field}=" . $usage_ele[$ele_index];
+%      }
+%    }
+%     
+%    $usage_ele[2]="<A HREF=\"../../view/cust_main.cgi?${cust_num}\" target=\"_blank\">${cust_last_name}</A>";
+%
+%    $usage_ele[3]="<A HREF=\"../../edit/usage_elec_prefilled_input.cgi?${pass_str}\" target=\"_blank\">$cust_num</A>";
+
+%    # insert TD tag
+%    my $str = join("\n", map("<TD>" . $_ . "</TD>", @usage_ele));
+%
+%    # let figure out if this particular usage data has already been entered 
+%    # into the usage_elec table
+%    # if it has, highlight it so the user can identify it
+%    # To check for this, we perform some quick check (prev_date, curr_date, 
+%    # prev_reading, curr_reading, and total_usage)
+%    my @exist_in_usage_elec; #identify usage exist in usage_elec table
+%    my @usages_history = qsearch ( {
+%                           'table'   => 'usage_elec',
+%                           'hashref' => { 'op'  => '=',
+%                                          'svcnum' => $svc_num
+%                                        },
+%                            # sort in DESCending order so it easier to splice
+%                            # the array in the next step
+%                           'extra_sql' => 'ORDER BY _date DESC'
+%                          } );
+%    #my $usage_history_no = scalar(@usages_history);
+%    my (%h_prev_date, %h_curr_date, %h_prev_read, %h_curr_read, 
+%        %h_total_usage);
+%    foreach my $usage (@usages_history) {
+%      $h_prev_date{$usage->prev_date} = 1;
+%      $h_curr_date{$usage->curr_date} = 1;
+%      $h_prev_read{$usage->prev_read} = 1;
+%      $h_curr_read{$usage->curr_read} = 1;
+%      $h_total_usage{$usage->total_usage} = 1;
+%    }
+%
+%    if ( exists $h_prev_date{str2time($usage_ele[9])} &&
+%         exists $h_curr_date{str2time($usage_ele[10])} &&
+%         exists $h_prev_read{$usage_ele[11]} &&
+%         exists $h_curr_read{$usage_ele[12]} &&
+%         exists $h_total_usage{$usage_ele[15]} ) {
+%      # when data already entered into usage_elec
+%      $str =  "<tr bgcolor=\"#9999ff\">$str</tr>";
+%
+%    }
+%    else {
+%      $str = "<tr>$str</tr>";
+%    }
+%
+%    push(@table_item,$str);
+%    push(@usage_cvs,join(',',@usage_ele)); #for exporting csv purposes
+%  }
+%
+%  my $str_cvs = join "<BR>",@usage_cvs; #for exporting csv purposes
+%  #print $str_cvs;
+    <!-- mason kludge -->
+    <% include("/elements/header.html","Import successful") %> 
+<table border="2" frame="border" rules="all">
+%
+% # print the heading
+% print "<tr class='maintitle'>"
+%  . join("\n", map("<th bgcolor=\"#88b2ce\">" . $_ . "</th>", @field_descriptions1))
+%  . join("\n", map("<th bgcolor=\"#ffff99\">" . $_ . "</th>", @field_descriptions2))
+%  . join("\n", map("<th bgcolor=\"#ff9999\">" . $_ . "</th>", @field_descriptions3))
+%  . join("\n", map("<th bgcolor=\"#66cc00\">" . $_ . "</th>", @field_descriptions4))
+%  . join("\n", map("<th bgcolor=\"#ff99cc\">" . $_ . "</th>", @field_descriptions5))
+%  . "</tr>\n";
+%
+% # print the table
+% foreach my $e (@table_item) {
+%   #print "<TR>$e</TR>";
+%   print $e;
+% }
+</table>
+%
+% # dumping CSV data out 
+% #foreach my $cvs_item (@usage_cvs) {
+% #  print "$cvs_item<BR>";
+% #}
+    
+%
+%  }
+%
+
diff --git a/httemplate/misc/process/cust_main-import-oldonp.cgi b/httemplate/misc/process/cust_main-import-oldonp.cgi
new file mode 100755 (executable)
index 0000000..9bc4a45
--- /dev/null
@@ -0,0 +1,35 @@
+%
+%
+%  my $fh = $cgi->upload('csvfile');
+%  #warn $cgi;
+%  #warn $fh;
+%
+%  my $error = defined($fh)
+%    ? FS::cust_main::batch_import_onp( {
+%        filehandle => $fh,
+%        agentnum   => scalar($cgi->param('agentnum')),
+%        refnum     => scalar($cgi->param('refnum')),
+%        pkgpart    => scalar($cgi->param('pkgpart')),
+%        #'fields'    => [qw( cust_pkg.setup dayphone first last address1 address2
+%        #                   city state zip comments                          )],
+%        'format'   => scalar($cgi->param('format')),
+%      } )
+%    : 'No file';
+%
+%  if ( $error ) {
+%    
+
+    <!-- mason kludge -->
+%
+%    eidiot($error);
+%#    $cgi->param('error', $error);
+%#    print $cgi->redirect( "${p}cust_main-import.cgi
+%  } else {
+%    
+
+    <!-- mason kludge -->
+    <% include("/elements/header.html",'Import successful') %> 
+%
+%  }
+%
+
diff --git a/httemplate/misc/process/qualified_liteup_customers.cgi b/httemplate/misc/process/qualified_liteup_customers.cgi
new file mode 100755 (executable)
index 0000000..640bbc6
--- /dev/null
@@ -0,0 +1,139 @@
+<% include("/elements/header.html",'LITEUP') %>
+%
+%  my $fh = $cgi->upload('textfile');
+%
+%  my $action = $cgi->param('action');
+%  #print '<br><br>'.$action.'<br><br>';
+%  # read the text file and test for accuracy
+%  # we are expection each line of the file to contain
+%  # ESIIDxxxxADDRESSxxxxCITYxxxxSTATEZIPxxxxLITEUPDURATION
+%  # whese x is multiple whitespace
+%  #
+%  my $line;
+%  my @newlist;
+%  my $i =0;
+%  while ( defined($line=<$fh>) ) {
+%    $newlist[$i] = [ split /\s{2,}/, $line ];
+%    $i++;
+%  }
+%
+%  if ($action eq 'Get Report') { #liteup audit
+%    my ($beg,$end) = FS::UI::Web::parse_beginning_ending($cgi);
+%    $beg = str2time("1/1/2008") unless ($beg);
+%    $end = str2time(`date`) unless ($end);
+%    print '<h3>Audit from ' . time2str("%D",$beg) .' through '. 
+%          ($end == 4294967295 ? 'NOW' : time2str("%D",$end)) .'</h3>';
+%
+%    my @cust_bills = qsearch ( {
+%             'table' => 'cust_bill_pkg_detail',
+%             'hashref' => { 'curr_date' => { 
+%                                   'op' => '>=',
+%                                  'value' => "$beg",
+%                                            }
+%                          },
+%             'extra_sql'=> "AND curr_date <= $end
+%                            AND discount1_rate > 0
+%                            ORDER BY esiid ASC",
+%                               });
+%    my %liteup_cust;
+%    foreach my $cust_bill_pkg (@cust_bills) {
+%      if ($cust_bill_pkg->discount1_rate) {
+%        $cust_bill_pkg->esiid =~ /^\s*(\d{4})\d+$/;
+%        $liteup_cust{$1}{$cust_bill_pkg->esiid}{$cust_bill_pkg->curr_date}{discount} = 
+%                                                 $cust_bill_pkg->discount1_total;
+%        $liteup_cust{$1}{$cust_bill_pkg->esiid}{$cust_bill_pkg->curr_date}{usage} = 
+%                                          int($cust_bill_pkg->energy_usage);
+%        # let get the custnum
+%        my $cust_bill = qsearchs( {
+%                         'table' => 'cust_bill',
+%                         'hashref' => { 'invnum' => { 'op' => '=',
+%                                                      'value' => $cust_bill_pkg->invnum,
+%                                                    }
+%                                      }
+%                               });
+%        $liteup_cust{$1}{$cust_bill_pkg->esiid}{$cust_bill_pkg->curr_date}{custnum} = 
+%                                       $cust_bill->custnum;
+%      }
+%    }
+
+%    foreach my $esiid_4 (sort keys %liteup_cust) {
+%      print '<br><FONT COLOR="#FF0000"><b>'. $esiid_4 .'</FONT></b><br>';
+%      my $total = 0;
+%      my $total_usage = 0;
+%      foreach my $esiid (sort keys %{$liteup_cust{$esiid_4}}) {
+%        print '----'. $esiid .'<br>';
+%        foreach my $date (sort keys %{$liteup_cust{$esiid_4}{$esiid}}) {
+%          print '&nbsp;'x10
+%               . $liteup_cust{$esiid_4}{$esiid}{$date}{custnum} 
+%               . ':'. time2str("%D",$date) 
+%               . ':'. $liteup_cust{$esiid_4}{$esiid}{$date}{usage} .'kWh' 
+%               . ':$'. $liteup_cust{$esiid_4}{$esiid}{$date}{discount} 
+%               . '<br>';
+%          $total += $liteup_cust{$esiid_4}{$esiid}{$date}{discount};
+%          $total_usage += $liteup_cust{$esiid_4}{$esiid}{$date}{usage};
+%        }
+%      }
+%      #printf "==<FONT COLOR="#FF0000"><b>%dkWh'  . $total_usage .'kWh'.':$'. $total .'</FONT></b><br>';
+%      printf "==<FONT COLOR='#FF0000'><b>%dkWh:\$%.2f</FONT></b><br>",$total_usage,$total;
+%    }
+%
+%  }
+%elsif ($action eq 'Process List') {
+%    print 'UNDER CONSTRUCTION<BR>';
+%#  my @cust_main = qsearch ( {
+%#                         'table'     => 'cust_main',
+%#                         'extra_sql' => 'ORDER BY custnum ASC'
+%#                        } );
+%
+%#  my %liteup_cust;
+%#  $i=1;
+%#  foreach my $cust (@cust_main) {
+%#    if ($i<2000) {
+%#      #print $cust->custnum . "=>" . $cust->first . "," . $cust->last . "<br>";
+%#      #print $cust->custnum . "<br>";
+%#      my @packages = get_packages($cust);
+%#      foreach my $cust_pkg (@packages) {
+%#        my $part_pkg = $cust_pkg->part_pkg;
+%##        print 'PKG: '. $part_pkg->pkg . "<br>";
+%#        my @part_pkg_option = $part_pkg->part_pkg_option;
+%#        my @cust_svc = $cust_pkg->cust_svc(3);
+%#        foreach my $custsvc (@cust_svc) {
+%#          my $liteup_discount;
+%#          foreach my $pkg_option (@part_pkg_option) {
+%##            print 'optionname:'.$pkg_option->optionname.'-----optionvalue:'
+%##                 .$pkg_option->optionvalue .'<br>';
+%#            if ($pkg_option->optionname eq 'rate1_discount' && 
+%#                $pkg_option->optionvalue) {
+%#              $liteup_discount = $pkg_option->optionvalue;
+%#            }
+%#          }
+%
+%#          my $svc_x = $custsvc->svc_x;
+%##          print "svcnum = " . $custsvc->svcnum . "<br>" if $custsvc;
+%##          print $svc_x->id . ':'. $svc_x->title .'<br>';
+
+%#          if ($svc_x->title eq 'ESIID' && $svc_x->id && $liteup_discount) {
+%#            print $cust->custnum . "=>" . $cust->first . "," . $cust->last . "<br>";
+%#            print 'PKG: '. $part_pkg->pkg . "<br>";
+%#            print $svc_x->id . ':'. $svc_x->title .'<br>';
+%#            print 'disount:' . $liteup_discount . '<br>;
+%#            $liteup_cust{$cust->custnum}{first} = $cust->first;
+%#            $liteup_cust{$cust->custnum}{last} = $cust->last;
+%#            $liteup_cust{$cust->custnum}{esiid} = $cust->esiid;
+%#            $liteup_cust{$cust->custnum}{discount} = $liteup_discount;
+%#          }
+%#        }
+%#      }
+%#    }
+%#    $i++;
+%#  }
+%
+%} #end elsif
+%#  sub get_packages {
+%#    my $cust_main = shift or return undef;
+%
+%
+%#    return $cust_main->ncancelled_pkgs();
+
+%#  }
+<% include('/elements/footer.html') %>
diff --git a/httemplate/misc/process/transaction810-import.cgi b/httemplate/misc/process/transaction810-import.cgi
new file mode 100755 (executable)
index 0000000..ab31bc6
--- /dev/null
@@ -0,0 +1,23 @@
+%
+%
+%  my $fh = $cgi->upload('csvfile');
+%  #warn $cgi;
+%  #warn $fh;
+%
+%  my $error = FS::transaction810::testing( {
+%        filehandle => $fh,
+%        'format'   => scalar($cgi->param('format')),
+%      } );
+%
+%  if ( $error ) {
+%    
+
+<b><% $error %></b> 
+%
+%  } else {
+%    
+<b>Not Successful!</b>
+%
+%  }
+%
+
diff --git a/httemplate/misc/qualified_liteup_customers.cgi b/httemplate/misc/qualified_liteup_customers.cgi
new file mode 100755 (executable)
index 0000000..d0aa992
--- /dev/null
@@ -0,0 +1,71 @@
+<% include("/elements/header.html",'LITEUP') %>
+
+<h3> <FONT COLOR="Blue">Process new Liteup List</FONT></h3>
+<FORM ACTION="process/qualified_liteup_customers.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+
+Import a text file from PUCT containg qualified LITEUP customers.
+<BR>
+The text file should contain ESIID, Address, City, StateZip, Qualifydate.  All seperated by 2 or more white spaces.  
+<BR><BR>
+ie.
+<BR>
+10032789488231455        3608 ORING         EDINBURG  TX78539   2008030120080731<BR>
+1008939284701838499830   2206 W REEN RD     HOUSTON   TX77067   2008030120080731
+<BR><BR>
+
+<% &ntable("#cccccc") %>
+
+<% include('/elements/tr-select-agent.html', '', #$agentnum,
+              'label'       => "<B>Agent</B>",
+              'empty_label' => 'Select agent',
+           )
+%>
+
+<!--
+<TR>
+  <TH ALIGN="right">Format</TH>
+  <TD>
+    <SELECT NAME="format">
+      <OPTION VALUE="simple">Simple
+      <OPTION VALUE="extended" SELECTED>Extended
+    </SELECT>
+  </TD>
+</TR>
+-->
+
+<TR>
+  <TH ALIGN="right">filename</TH>
+  <TD><INPUT TYPE="file" NAME="textfile"></TD>
+</TR>
+% #include('/elements/tr-select-part_referral.html')
+%
+
+</TABLE>
+<BR>
+
+<INPUT STYLE="background-color:lightgreen" TYPE="submit" name="action" VALUE="Process List">
+
+<BR><BR>
+<hr color="#CC2277" size="5">
+
+<h3> <FONT COLOR="Blue">Audit Liteup Program</FONT></h3>
+
+<TABLE>
+<% include( '/elements/tr-input-beginning_ending.html' ) %>
+</TABLE>
+
+<BR>
+<INPUT STYLE="background-color:lightgreen" TYPE="submit" name="action" VALUE="Get Report">
+
+<BR><BR>
+<hr color="#CC2277" size="5">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%once>
+my $req = qq!<font color="#ff0000">*</font>!;
+</%once>
+
+
diff --git a/httemplate/misc/transaction810-import.cgi b/httemplate/misc/transaction810-import.cgi
new file mode 100755 (executable)
index 0000000..e957666
--- /dev/null
@@ -0,0 +1,76 @@
+<% include("/elements/header.html",'Batch Customer Import') %>
+
+<FORM ACTION="process/transaction810-import.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+
+Import a CSV file containing 810 data.
+<BR><BR>
+
+<!-- Simple file format is CSV, with the following field order: <i>duns, inv_num, 867_usage, esiid, tdsp, due_date, inv_date, usage_kwatts, srvc_from_date, srvc_to_date, puct_fund, billed_demand, measure_demand, bill_status, billing_type, 997_ack</i>
+<BR><BR> -->
+
+Extended file format is CSV, with the 
+<BR><BR>
+
+<%$req%> Required fields
+<BR><BR>
+
+[1] This field has special treatment upon import: If a string is passed instead
+of an integer, the string is searched for and if necessary auto-created in the
+target table.
+<BR><BR>
+
+[2] <i>username</i> and <i>_password</i> are required if <i>pkgpart</i> is specified.
+<BR><BR>
+
+<% &ntable("#cccccc") %>
+
+<% include('/elements/tr-select-agent.html', '', #$agentnum,
+              'label'       => "<B>Agent</B>",
+              'empty_label' => 'Select agent',
+           )
+%>
+
+<TR>
+  <TH ALIGN="right">Format</TH>
+  <TD>
+    <SELECT NAME="format">
+<!--      <OPTION VALUE="simple">Simple -->
+      <OPTION VALUE="extended" SELECTED>Extended
+    </SELECT>
+  </TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">CSV filename</TH>
+  <TD><INPUT TYPE="file" NAME="csvfile"></TD>
+</TR>
+% #include('/elements/tr-select-part_referral.html')
+%
+
+
+<!--
+<TR>
+  <TH>First package</TH>
+  <TD>
+    <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION>
+% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { 
+
+       <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION>
+% } 
+
+    </SELECT>
+  </TD>
+</TR>
+-->
+
+</TABLE>
+<BR><BR>
+
+<INPUT TYPE="submit" VALUE="Import">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%once>
+my $req = qq!<font color="#ff0000">*</font>!;
+</%once>
diff --git a/httemplate/misc/usage_elec_prefilled_input.cgi b/httemplate/misc/usage_elec_prefilled_input.cgi
new file mode 100755 (executable)
index 0000000..43b45d0
--- /dev/null
@@ -0,0 +1,245 @@
+%my $debug=0; # toggle debug
+%my ($svcnum,  $p_prev_date, $p_curr_date, $p_prev_reading, $p_curr_reading,
+%    $p_tdsp, $p_meter_mult, $p_total_usage, $p_measured_demand, 
+%    $p_billed_demand, $p_svcnum, $p_entry_date, $p_meter_number,
+%    $p_first, $p_last, $p_balance, $p_last_billed);
+%my ($pkgnum, $svcpart, $svc_external );
+%my @field_descriptions = ( 'prev date', 'curr date', 'prev reading',
+%                           'curr reading', 'tdsp', 'meter mult',
+%                           'total usage', 'measured demand', 'billed demand',
+%                           'svcnum', 'entry date', 'meter number' );
+%my @field_name = qw / prev_date curr_date prev_read curr_read tdsp 
+%                      meter_multiplier total_usage measured_demand
+%                      billed_demand svcnum _date meter_number /;
+%my $date_exception = '(prev_date|curr_date|_date)';
+%
+%if ( $cgi->param('error') ) {
+%  ### handle error call
+%  $svcnum = $cgi->param('svcnum');
+%}
+%else {
+%
+%  my($query) = $cgi->keywords;
+%#  $query =~ /^(\d+)$/ or die "unparsable svcnum";
+%  #$svcnum=$1;
+%
+%  $p_prev_date        = $cgi->param('p_prev_date');
+%  $p_curr_date        = $cgi->param('p_curr_date');
+%  $p_prev_reading     = $cgi->param('p_prev_reading');
+%  $p_curr_reading     = $cgi->param('p_curr_reading');
+%  $p_tdsp             = $cgi->param('p_tdsp');
+%  $p_meter_mult       = $cgi->param('p_meter_mult');
+%  $p_total_usage      = $cgi->param('p_total_usage');
+%  $p_measured_demand  = $cgi->param('p_measured_demand');
+%  $p_billed_demand    = $cgi->param('p_billed_demand');
+%  $p_svcnum = $svcnum = $cgi->param('p_svcnum');
+%  $p_first            = $cgi->param('p_first');
+%  $p_last             = $cgi->param('p_last');
+%  $p_balance          = $cgi->param('p_balance');
+%  $p_last_billed      = $cgi->param('p_last_billed');
+%  #$svcnum = $cgi->param('p_meter_number');
+%}
+%
+%# this is sample data for print in case no previous record of usage_elec
+%my @sample_data = ( '20070201', '20070228', '10000', '100100', '76.50',
+%                    '5', '500', '179', '220', "$svcnum", 'NA', '030234972LM');
+%
+%### this is where i start
+%### 
+%### let gather all the info from usage_elec for the particular 'svcnum'
+%###
+%my $p1 = popurl(1);
+%
+%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+%      "</FONT>"
+%  if $cgi->param('error');
+%
+%print qq!<FORM ACTION="${p1}process/usage_elec_manual_input.cgi" METHOD=POST>!;
+%
+%# print header
+%#print header("Manually Adding Record to usage_elec Table", '');
+%print header("NAME: ${p_first} ${p_last}", '');
+%print header("BALANCE: \$${p_balance}  LAST BILLED: ${p_last_billed}", '');
+%
+%#display
+%#
+%#
+<TABLE BORDER=1>
+% 
+% # -ctran 04/10/08
+% # change getting previous 10 record to 13 so we can see at least 1 year
+% # worth of transaction
+% # get the previous 13 usage_elec items
+% my @usage_obj = FS::usage_elec::query_usage($svcnum, 13); 
+%
+% # print the heading
+% print "<TR bgcolor=#88b2ce class='maintitle'>"
+%      . join("\n", map("<TH>" . $_ . "</TH>", @field_descriptions))
+%      . "</TR>\n";
+%
+% if (@usage_obj) {
+%   foreach my $usage (@usage_obj) {
+%     # fill @usage_ele with data order by @field_name
+%     my @usage_ele = ();
+%     foreach my $field (@field_name) {
+%       if ( $field =~ /$date_exception/ ) {
+%         # exception handling of converting time to string
+%         push(@usage_ele,time2str("%Y%m%d",$usage->$field));
+%       }
+%       else {
+%#debug: field= <% $field %> = <% $usage->$field %><BR>
+%         push(@usage_ele, $usage->$field); 
+%       }
+%     }
+%
+%     print "<TR bgcolor=#e8e8ea class='mainbody'>" 
+%          . join("\n", map("<TD>" . $_ . "</TD>", @usage_ele))
+%          . "</TR>\n";
+%   } 
+% }
+%
+% ###
+% ### gathering pre-filled information
+% ###
+%
+% my ($h_prev_date, $h_prev_read, $h_tdsp, $h_meter_multiplier,
+%     $h_measured_demand, $h_billed_demand, $h_svcnum, $h_meter_number);
+%
+% if (@usage_obj) {
+%  # fill in all the history data
+%  my $lindex = $#usage_obj;
+%  $h_prev_date = time2str("%Y%m%d",$usage_obj[$lindex]->curr_date);
+%  $h_prev_read = $usage_obj[$lindex]->curr_read;
+%  $h_tdsp = $usage_obj[$lindex]->tdsp;
+%  $h_meter_multiplier = $usage_obj[$lindex]->meter_multiplier;
+%  $h_measured_demand = $usage_obj[$lindex]->measured_demand;
+%  $h_billed_demand = $usage_obj[$lindex]->billed_demand;
+%  $h_svcnum = $usage_obj[$lindex]->svcnum;
+%  $h_meter_number = $usage_obj[$lindex]->meter_number;
+% }
+% 
+% # let figure out if tdsp is charge or not (only charge for BUSINESS)
+% my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$p_svcnum});
+% my $part_svc = qsearchs('part_svc',{'svcpart' => $cust_svc->svcpart});
+% $p_tdsp = '0.00' if ($part_svc->svc !~ /BUSINESS/i);
+%
+% # this hash store info to configure the table with text box for input
+% # size - [int] how big textbox
+% # value - [alpha numeric] default value of the text box
+% # extra - [alpha numeric] other option for text box.  I.E. READONLY
+% #         mean the text box is a readonly
+% my %field_info = (
+%                   prev_date => {
+%                                  'size'     => '8', 
+%                                  'value'    => $p_prev_date, 
+%                                },
+%                   curr_date => { 'size'     => '8', 
+%                                  'value'    => $p_curr_date, 
+%                                },
+%                   prev_read => {
+%                                  'size'     => '8', 
+%                                  'value'    => $p_prev_reading, 
+%                                },
+%                   curr_read => { 'size'     => '8',
+%                                  'value'    => $p_curr_reading, 
+%                                },
+%                   tdsp      => {
+%                                  'size'     => '8', 
+%                                  'value'    => $p_tdsp/100,
+%                                },
+%                   meter_multiplier => {
+%                                  'size'  => '4', 
+%                                  'value' => $p_meter_mult,
+%                                       },
+%                   total_usage => { 'size'     => '6',
+%                                    'value'    => $p_total_usage,
+%                                  },
+%                   measured_demand => {
+%                                  'size'  => '4', 
+%                                  'value' => $p_measured_demand,
+%                                      },
+%                   billed_demand => {
+%                                  'size'  => '4', 
+%                                  'value' => $p_billed_demand,
+%                                    },
+%                   svcnum => {
+%                               'size'     => '6', 
+%                               'value'    => $p_svcnum,
+%                               'extra'    => 'READONLY'
+%                             },
+%                   _date => {
+%                              'size'     => '8', 
+%                              'value'    => 'N/A',
+%                              'extra'    => 'READONLY'
+%                             },
+%                   meter_number => {
+%                              'size'     => '14', 
+%                              'value'    => $h_meter_number,
+%                                   },
+%                 );
+%
+%
+% # input box for entry
+% print qq !<TR bgcolor=#e8e8ea class='mainbody'>!; 
+% my $input_style = 'STYLE="color:#000000; background-color: #FFFFCC;"';
+% foreach my $field (@field_name) {
+%   my $txt = '';
+%   $txt .= ' SIZE=' . $field_info{$field}->{'size'} 
+%                                   if (exists($field_info{$field}->{'size'}));
+%   $txt .= ' VALUE="' . $field_info{$field}->{'value'} . '"' 
+%                                   if (exists($field_info{$field}->{'value'}));
+%   $txt .= ' ' . $field_info{$field}->{'extra'} 
+%                                   if (exists($field_info{$field}->{'extra'}));
+%   if ($field eq 'meter_multiplier') {
+%     print qq !
+%               <TD>
+%                <TABLE>
+%                 <TD>
+%                   <INPUT TYPE="text" $input_style NAME="$field" $txt>
+%                 </TD>
+%                 <TD>
+%                   <INPUT TYPE="checkbox" NAME="ignore_meter_multiplier">Ignore<P>
+%                 </TD>
+%                </TABLE>
+%               </TD>
+%              !;
+%   }
+%   else {
+%     print qq !
+%               <TD>
+%               <INPUT TYPE="text" $input_style NAME="$field" $txt>
+%               </TD>
+%              !;
+%  }
+% } 
+% print "</TR>\n";
+% 
+
+</TABLE><BR>
+%print "<BR>measured demand = ",$h_measured_demand,"\n<BR>" if ($debug);
+%
+<INPUT TYPE="submit" VALUE="Submit">
+<INPUT TYPE="Reset" VALUE="Clear">
+<INPUT TYPE=BUTTON OnClick="$cgi->redirect(popurl(2)."view/svc_external.cgi?$svcnum")"
+       VALUE="Cancel">
+%
+% print qq !
+%   <br><br>
+%   prev_date, curr_date -
+%               8 digit in format of yyyymmdd (y-year m-month d-date)<br>
+%   prev_read, curr_read - positive interger. Also, curr_read > prev_read
+%                          Unless meter multiplier ignore value is set.  In
+%                          this case, this condition will be ignore.<br>
+%   tdsp - an dollar amount w/wo cent<br>
+%   meter_multiplier - positive integer<br>
+%   total_usage -
+%           should equal (total_usage = (prev_read-curr_read) * meter_multiplier)
+%           unless meter multiplier ignore value is set<br>
+%   measured_demand - positive integer<br>
+%   billed_demand - positive integer<br>
+% !;
+%
+    </FORM>
+  </BODY>
+</HTML>
+
index a340b7f..7522b8e 100644 (file)
@@ -56,6 +56,7 @@ unless ( $error ) { # if ($access_user) {
                       show_pkgnum show_confitem_counts export_getsettings
                       show_db_profile save_db_profile
                       height width availHeight availWidth colorDepth
+                      dashboard_customer_history_length
                     );
 
   foreach (@paramlist) {
index ab76c3b..0e24e6a 100644 (file)
@@ -53,6 +53,22 @@ Interface
       </SELECT>
     </TD>
   </TR>
+
+  <TR>
+    <TH ALIGN="right">Dashboard customer history: </TH>
+    <TD COLSPAN=2>
+      <SELECT NAME="dashboard_customer_history_length">
+%       foreach my $view ( qw( 5 10 15 20 25 ) ) {
+%         my $selected =
+%           $customer_views{$view} eq
+%               $curuser->option('dashboard_customer_history_length')
+%             ? 'SELECTED'
+%             : '';
+          <OPTION VALUE="<%$view%>" <%$selected%>><%$view%></OPTION>
+%       }
+      </SELECT>
+    </TD>
+  </TR>
   
   <TR>
     <TH ALIGN="right" COLSPAN=1>Disable HTML editor for customer notes: </TH>
index 51e47e0..c1639fd 100755 (executable)
@@ -31,6 +31,12 @@ my $cust_bill = qsearchs({
 });
 die "Invoice #$invnum not found!" unless $cust_bill;
 
+if ( $notice_name eq 'Record' ) {
+  $opt{base}='rec';
+} elsif ( $notice_name eq 'Record Ignoring DUE DATE' ) {
+  $opt{base}='rec';
+  $opt{ignore_due_date}=1;
+}
 my $pdf = $cust_bill->print_pdf(\%opt);
 
 http_header('Content-Type' => 'application/pdf' );
index ce8d96a..4e97bb5 100755 (executable)
 
   <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $link %>">View typeset invoice PDF</A>
   <BR><BR>
+% if ( $conf->exists('svc_elec_features') ) { 
+% my $reclink = "invnum=$invnum";
+% $reclink .= ';template='. uri_escape($template) if $template;
+  <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% "$reclink;notice_name=Record" %>">View Record</A><BR>
+  <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% "$reclink;notice_name=Record Ignoring DUE DATE" %>">View Record Ignoring DUE DATE</A>
+<BR><BR>
+
+%   } 
+
 % } 
 
 % my $br = 0;
index 77679d8..cee0262 100644 (file)
 
 
 </TABLE></TD></TR></TABLE>
+<BR>
+% if ($conf->exists('svc_elec_features')) {
+<A HREF="<%$p%>edit/usage_elec_manual_input.cgi?<%$svcnum%>">Manually manipulate electric usage</A><BR>
+% }
 <BR><% joblisting({'svcnum'=>$svcnum}, 1) %>
 
 <% include('/elements/footer.html') %>