summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjeff <jeff>2010-08-02 19:49:26 +0000
committerjeff <jeff>2010-08-02 19:49:26 +0000
commit0a3b346806615bd893048d2a5c26f8d126a12f4b (patch)
tree6912cbc88774bfa513e95a58e05e7ce76fbc04c1
parent995a145c931164347683071c95c6754379d36604 (diff)
add svc_elec_features merged from reference code RT#7643svc_elec_features
-rw-r--r--FS/FS/Conf.pm17
-rw-r--r--FS/FS/Mason.pm6
-rw-r--r--FS/FS/Schema.pm159
-rw-r--r--FS/FS/cust_bill.pm192
-rw-r--r--FS/FS/cust_bill_pkg.pm32
-rw-r--r--FS/FS/cust_bill_pkg_detail.pm68
-rw-r--r--FS/FS/cust_main.pm780
-rwxr-xr-xFS/FS/elec_general.pm121
-rw-r--r--FS/FS/part_pkg.pm13
-rwxr-xr-xFS/FS/part_pkg/business_elec_generic.pm96
-rwxr-xr-xFS/FS/part_pkg/energy_base_discount_500kwh.pm150
-rwxr-xr-xFS/FS/part_pkg/energy_base_discount_tiers.pm160
-rwxr-xr-xFS/FS/part_pkg/residential_elec_generic.pm94
-rwxr-xr-xFS/FS/part_pkg/residential_elec_generic_var.pm113
-rwxr-xr-xFS/FS/svc_elec.pm138
-rwxr-xr-xFS/FS/transaction810.pm307
-rwxr-xr-xFS/FS/transaction867.pm300
-rwxr-xr-xFS/FS/usage_elec.pm647
-rwxr-xr-xFS/FS/usage_elec_transaction867.pm124
-rw-r--r--FS/MANIFEST13
-rwxr-xr-xFS/t/elec_general.t5
-rwxr-xr-xFS/t/svc_elec.t5
-rwxr-xr-xFS/t/transaction810.t5
-rwxr-xr-xFS/t/transaction867.t5
-rwxr-xr-xFS/t/usage_elec.t5
-rwxr-xr-xFS/t/usage_elec_transaction867.t5
-rwxr-xr-xconf/rec_latex315
-rwxr-xr-xhttemplate/edit/process/usage_elec_manual_input.cgi225
-rwxr-xr-xhttemplate/edit/usage_elec_manual_input.cgi213
-rw-r--r--httemplate/elements/menu.html42
-rw-r--r--httemplate/index.html5
-rwxr-xr-xhttemplate/misc/cust_edi_data-onp.cgi57
-rwxr-xr-xhttemplate/misc/cust_main-import-oldonp.cgi68
-rw-r--r--httemplate/misc/payment.cgi2
-rwxr-xr-xhttemplate/misc/process/cust_edi_data-onp.cgi182
-rwxr-xr-xhttemplate/misc/process/cust_main-import-oldonp.cgi35
-rwxr-xr-xhttemplate/misc/process/qualified_liteup_customers.cgi139
-rwxr-xr-xhttemplate/misc/process/transaction810-import.cgi23
-rwxr-xr-xhttemplate/misc/qualified_liteup_customers.cgi71
-rwxr-xr-xhttemplate/misc/transaction810-import.cgi76
-rwxr-xr-xhttemplate/misc/usage_elec_prefilled_input.cgi245
-rw-r--r--httemplate/pref/pref-process.html1
-rw-r--r--httemplate/pref/pref.html16
-rwxr-xr-xhttemplate/view/cust_bill-pdf.cgi6
-rwxr-xr-xhttemplate/view/cust_bill.cgi9
-rw-r--r--httemplate/view/svc_external.cgi4
46 files changed, 5257 insertions, 37 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index ce8bd29..44140ec 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -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',
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 0f14150..f75633a 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -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 ) {
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 60d2bce..3124f81 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -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', '', '', '', '',
diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm
index 4bd9aa1..fcc2a65 100644
--- a/FS/FS/cust_bill.pm
+++ b/FS/FS/cust_bill.pm
@@ -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 ) ];
}
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
index d396f82..0a1d422 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -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',
+ });
}
diff --git a/FS/FS/cust_bill_pkg_detail.pm b/FS/FS/cust_bill_pkg_detail.pm
index 4d9ee81..14b85cd 100644
--- a/FS/FS/cust_bill_pkg_detail.pm
+++ b/FS/FS/cust_bill_pkg_detail.pm
@@ -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)) {
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 002b0c1..19478e1 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -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
index 0000000..293d013
--- /dev/null
+++ b/FS/FS/elec_general.pm
@@ -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;
+
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
index f278d5e..92b2b23 100644
--- a/FS/FS/part_pkg.pm
+++ b/FS/FS/part_pkg.pm
@@ -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
index 0000000..14d8070
--- /dev/null
+++ b/FS/FS/part_pkg/business_elec_generic.pm
@@ -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
index 0000000..22f3f58
--- /dev/null
+++ b/FS/FS/part_pkg/energy_base_discount_500kwh.pm
@@ -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
index 0000000..62f6333
--- /dev/null
+++ b/FS/FS/part_pkg/energy_base_discount_tiers.pm
@@ -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
index 0000000..b88ed64
--- /dev/null
+++ b/FS/FS/part_pkg/residential_elec_generic.pm
@@ -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
index 0000000..584de70
--- /dev/null
+++ b/FS/FS/part_pkg/residential_elec_generic_var.pm
@@ -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
index 0000000..c86a72c
--- /dev/null
+++ b/FS/FS/svc_elec.pm
@@ -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
index 0000000..569d9b8
--- /dev/null
+++ b/FS/FS/transaction810.pm
@@ -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
index 0000000..4b3bed0
--- /dev/null
+++ b/FS/FS/transaction867.pm
@@ -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
index 0000000..55c45de
--- /dev/null
+++ b/FS/FS/usage_elec.pm
@@ -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
index 0000000..6373eca
--- /dev/null
+++ b/FS/FS/usage_elec_transaction867.pm
@@ -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;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 1b2e08d..5bcbaff 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -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
index 0000000..9dfb1d2
--- /dev/null
+++ b/FS/t/elec_general.t
@@ -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
index 0000000..e7fda34
--- /dev/null
+++ b/FS/t/svc_elec.t
@@ -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
index 0000000..98c6b88
--- /dev/null
+++ b/FS/t/transaction810.t
@@ -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
index 0000000..699de3d
--- /dev/null
+++ b/FS/t/transaction867.t
@@ -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
index 0000000..4824919
--- /dev/null
+++ b/FS/t/usage_elec.t
@@ -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
index 0000000..086edff
--- /dev/null
+++ b/FS/t/usage_elec_transaction867.t
@@ -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
index 0000000..d8b1bc4
--- /dev/null
+++ b/conf/rec_latex
@@ -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
index 0000000..1a62c39
--- /dev/null
+++ b/httemplate/edit/process/usage_elec_manual_input.cgi
@@ -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
index 0000000..7f9d9fa
--- /dev/null
+++ b/httemplate/edit/usage_elec_manual_input.cgi
@@ -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>
+
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index a5bcdeb..aa067e6 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -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'} =
diff --git a/httemplate/index.html b/httemplate/index.html
index 5b550db..a33ffb5 100644
--- a/httemplate/index.html
+++ b/httemplate/index.html
@@ -19,7 +19,10 @@
% 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
index 0000000..985637b
--- /dev/null
+++ b/httemplate/misc/cust_edi_data-onp.cgi
@@ -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
index 0000000..1409e0a
--- /dev/null
+++ b/httemplate/misc/cust_main-import-oldonp.cgi
@@ -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>
diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi
index 813b560..f587bc2 100644
--- a/httemplate/misc/payment.cgi
+++ b/httemplate/misc/payment.cgi
@@ -182,7 +182,7 @@
<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
index 0000000..4295a8d
--- /dev/null
+++ b/httemplate/misc/process/cust_edi_data-onp.cgi
@@ -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
index 0000000..9bc4a45
--- /dev/null
+++ b/httemplate/misc/process/cust_main-import-oldonp.cgi
@@ -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
index 0000000..640bbc6
--- /dev/null
+++ b/httemplate/misc/process/qualified_liteup_customers.cgi
@@ -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
index 0000000..ab31bc6
--- /dev/null
+++ b/httemplate/misc/process/transaction810-import.cgi
@@ -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
index 0000000..d0aa992
--- /dev/null
+++ b/httemplate/misc/qualified_liteup_customers.cgi
@@ -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
index 0000000..e957666
--- /dev/null
+++ b/httemplate/misc/transaction810-import.cgi
@@ -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
index 0000000..43b45d0
--- /dev/null
+++ b/httemplate/misc/usage_elec_prefilled_input.cgi
@@ -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>
+
diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html
index a340b7f..7522b8e 100644
--- a/httemplate/pref/pref-process.html
+++ b/httemplate/pref/pref-process.html
@@ -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) {
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
index ab76c3b..0e24e6a 100644
--- a/httemplate/pref/pref.html
+++ b/httemplate/pref/pref.html
@@ -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>
diff --git a/httemplate/view/cust_bill-pdf.cgi b/httemplate/view/cust_bill-pdf.cgi
index 51e47e0..c1639fd 100755
--- a/httemplate/view/cust_bill-pdf.cgi
+++ b/httemplate/view/cust_bill-pdf.cgi
@@ -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' );
diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi
index ce8d96a..4e97bb5 100755
--- a/httemplate/view/cust_bill.cgi
+++ b/httemplate/view/cust_bill.cgi
@@ -78,6 +78,15 @@
<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;
diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi
index 77679d8..cee0262 100644
--- a/httemplate/view/svc_external.cgi
+++ b/httemplate/view/svc_external.cgi
@@ -23,6 +23,10 @@
</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') %>