From: jeff Date: Mon, 2 Aug 2010 19:49:26 +0000 (+0000) Subject: add svc_elec_features merged from reference code RT#7643 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=refs%2Fheads%2Fsvc_elec_features add svc_elec_features merged from reference code RT#7643 --- diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index ce8bd296e..44140ecfa 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 billing documentation 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 0f1415009..f75633abb 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 60d2bcef5..3124f8133 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 4bd9aa16a..fcc2a65b5 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 d396f8239..0a1d422b7 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 4d9ee8191..14b85cd40 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 002b0c1d1..19478e13c 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.
$inpstr" + if ($cn !~ /^\d{9}$/); + + return "error: custnum '$cn' must start with a 9 or 1.
$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" + ."
$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" + ."
$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" + ."
$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" + ."
$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." + ."
$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 000000000..293d01321 --- /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 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, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index f278d5ebd..92b2b2376 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 000000000..14d80705d --- /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 000000000..22f3f585f --- /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 000000000..62f633396 --- /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 000000000..b88ed6437 --- /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 000000000..584de706a --- /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 000000000..c86a72cc5 --- /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 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, 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 000000000..569d9b87c --- /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 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, 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 000000000..4b3bed0d7 --- /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 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, 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 000000000..55c45def1 --- /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 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, 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 000000000..6373eca1d --- /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 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, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index 1b2e08df8..5bcbaffee 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 000000000..9dfb1d260 --- /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 000000000..e7fda3436 --- /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 000000000..98c6b88b6 --- /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 000000000..699de3de4 --- /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 000000000..4824919c0 --- /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 000000000..086edff48 --- /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 000000000..d8b1bc4a8 --- /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 000000000..1a62c3937 --- /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 %>
+% +%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
" +% ." of being a dollar amount w/wo cent value.
"; +% $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
" +% ." of being a positive integer.
"; +% $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
" +% ." total_usage = (current_reading - previous_reading) * meter_multiplier
" +% ." unless the meter multiplier ignore flag '$multiplier_ignore_flag' is set.
"; +% $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
" +% ." of being a positive integer.
"; +% $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
" +% ." of being a positive integer.
"; +% $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
" +% ." of being a alpha numeric value.
"; +% $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 000000000..7f9d9fa72 --- /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!Error: !, $cgi->param('error'), +% "" +% if $cgi->param('error'); +% +%print qq!
!; +% +%# print header +%print header("Manually Adding Record to usage_elec Table", ''); +% +%#display +%# +%# + +% +% # -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 "" +% . join("\n", map("", @field_descriptions)) +% . "\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 %>
+% push(@usage_ele, $usage->$field); +% } +% } +% +% print "" +% . join("\n", map("", @usage_ele)) +% . "\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 !!; +% 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 ! +% +% !; +% } +% else { +% print qq ! +% +% !; +% } +% } +% print "\n"; +% + +
" . $_ . "
" . $_ . "
+% +% +% +%
+% +% +% Ignore

+%

+%
+% +%

+%print "
measured demand = ",$h_measured_demand,"\n
" if ($debug); +% + + + +% +% print qq ! +%

+% prev_date, curr_date - +% 8 digit in format of yyyymmdd (y-year m-month d-date)
+% 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.
+% 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
+% !; +% +
+ + + diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index a5bcdeb19..aa067e69e 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 5b550dba7..a33ffb5ba 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 000000000..985637b0c --- /dev/null +++ b/httemplate/misc/cust_edi_data-onp.cgi @@ -0,0 +1,57 @@ +<% include("/elements/header.html",'Import EDI Info') %> + +
+ +Import a CSV file containing customer records. +

+ + + + +

+ +<%$req%> Required fields +

+ +<% &ntable("#cccccc") %> + +<% include('/elements/tr-select-agent.html', '', #$agentnum, + 'label' => "Agent", + 'empty_label' => 'Select agent', + ) +%> + + + Format + + + + + + + CSV filename + + +% #include('/elements/tr-select-part_referral.html') +% + + + + + +

+ + +
+ +<% include('/elements/footer.html') %> + +<%once> +my $req = qq!*!; + diff --git a/httemplate/misc/cust_main-import-oldonp.cgi b/httemplate/misc/cust_main-import-oldonp.cgi new file mode 100755 index 000000000..1409e0a5b --- /dev/null +++ b/httemplate/misc/cust_main-import-oldonp.cgi @@ -0,0 +1,68 @@ +<% include("/elements/header.html",'Batch Customer Import') %> + +
+ +Import a CSV file containing customer records. +

+ + + +Extended file format is CSV, with the following field order: 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%> +

+ +<%$req%> Required fields +

+ +<% &ntable("#cccccc") %> + +<% include('/elements/tr-select-agent.html', '', #$agentnum, + 'label' => "Agent", + 'empty_label' => 'Select agent', + ) +%> + + + Format + + + + + + + CSV filename + + +% #include('/elements/tr-select-part_referral.html') +% + + + + + +

+ + +
+ +<% include('/elements/footer.html') %> + +<%once> +my $req = qq!*!; + diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi index 813b560bd..f587bc2d2 100644 --- a/httemplate/misc/payment.cgi +++ b/httemplate/misc/payment.cgi @@ -182,7 +182,7 @@ % } -% if ( $conf->exists('show_ss') ) { +% if ( $conf->exists('show_ss') && !$conf->exists('svc_elec_features') ) { Account holder
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 000000000..4295a8d79 --- /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/) { +% + + +% +% 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]="${cust_last_name}"; +% +% $usage_ele[3]="$cust_num"; + +% # insert TD tag +% my $str = join("\n", map("" . $_ . "", @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 = "$str"; +% +% } +% else { +% $str = "$str"; +% } +% +% push(@table_item,$str); +% push(@usage_cvs,join(',',@usage_ele)); #for exporting csv purposes +% } +% +% my $str_cvs = join "
",@usage_cvs; #for exporting csv purposes +% #print $str_cvs; + + <% include("/elements/header.html","Import successful") %> + +% +% # print the heading +% print "" +% . join("\n", map("", @field_descriptions1)) +% . join("\n", map("", @field_descriptions2)) +% . join("\n", map("", @field_descriptions3)) +% . join("\n", map("", @field_descriptions4)) +% . join("\n", map("", @field_descriptions5)) +% . "\n"; +% +% # print the table +% foreach my $e (@table_item) { +% #print "$e"; +% print $e; +% } +
" . $_ . "" . $_ . "" . $_ . "" . $_ . "" . $_ . "
+% +% # dumping CSV data out +% #foreach my $cvs_item (@usage_cvs) { +% # print "$cvs_item
"; +% #} + +% +% } +% + 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 000000000..9bc4a453b --- /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 ) { +% + + +% +% eidiot($error); +%# $cgi->param('error', $error); +%# print $cgi->redirect( "${p}cust_main-import.cgi +% } else { +% + + + <% 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 000000000..640bbc6f4 --- /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 '

'.$action.'

'; +% # 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 '

Audit from ' . time2str("%D",$beg) .' through '. +% ($end == 4294967295 ? 'NOW' : time2str("%D",$end)) .'

'; +% +% 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 '
'. $esiid_4 .'
'; +% my $total = 0; +% my $total_usage = 0; +% foreach my $esiid (sort keys %{$liteup_cust{$esiid_4}}) { +% print '----'. $esiid .'
'; +% foreach my $date (sort keys %{$liteup_cust{$esiid_4}{$esiid}}) { +% print ' '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} +% . '
'; +% $total += $liteup_cust{$esiid_4}{$esiid}{$date}{discount}; +% $total_usage += $liteup_cust{$esiid_4}{$esiid}{$date}{usage}; +% } +% } +% #printf "==%dkWh' . $total_usage .'kWh'.':$'. $total .'
'; +% printf "==%dkWh:\$%.2f
",$total_usage,$total; +% } +% +% } +%elsif ($action eq 'Process List') { +% print 'UNDER CONSTRUCTION
'; +%# 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 . "
"; +%# #print $cust->custnum . "
"; +%# my @packages = get_packages($cust); +%# foreach my $cust_pkg (@packages) { +%# my $part_pkg = $cust_pkg->part_pkg; +%## print 'PKG: '. $part_pkg->pkg . "
"; +%# 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 .'
'; +%# 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 . "
" if $custsvc; +%## print $svc_x->id . ':'. $svc_x->title .'
'; + +%# if ($svc_x->title eq 'ESIID' && $svc_x->id && $liteup_discount) { +%# print $cust->custnum . "=>" . $cust->first . "," . $cust->last . "
"; +%# print 'PKG: '. $part_pkg->pkg . "
"; +%# print $svc_x->id . ':'. $svc_x->title .'
'; +%# print 'disount:' . $liteup_discount . '
; +%# $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 000000000..ab31bc615 --- /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 ) { +% + +<% $error %> +% +% } else { +% +Not Successful! +% +% } +% + diff --git a/httemplate/misc/qualified_liteup_customers.cgi b/httemplate/misc/qualified_liteup_customers.cgi new file mode 100755 index 000000000..d0aa99226 --- /dev/null +++ b/httemplate/misc/qualified_liteup_customers.cgi @@ -0,0 +1,71 @@ +<% include("/elements/header.html",'LITEUP') %> + +

Process new Liteup List

+
+ +Import a text file from PUCT containg qualified LITEUP customers. +
+The text file should contain ESIID, Address, City, StateZip, Qualifydate. All seperated by 2 or more white spaces. +

+ie. +
+10032789488231455 3608 ORING EDINBURG TX78539 2008030120080731
+1008939284701838499830 2206 W REEN RD HOUSTON TX77067 2008030120080731 +

+ +<% &ntable("#cccccc") %> + +<% include('/elements/tr-select-agent.html', '', #$agentnum, + 'label' => "Agent", + 'empty_label' => 'Select agent', + ) +%> + + + + + filename + + +% #include('/elements/tr-select-part_referral.html') +% + + +
+ + + +

+
+ +

Audit Liteup Program

+ + +<% include( '/elements/tr-input-beginning_ending.html' ) %> +
+ +
+ + +

+
+ +
+ +<% include('/elements/footer.html') %> + +<%once> +my $req = qq!*!; + + + diff --git a/httemplate/misc/transaction810-import.cgi b/httemplate/misc/transaction810-import.cgi new file mode 100755 index 000000000..e957666b4 --- /dev/null +++ b/httemplate/misc/transaction810-import.cgi @@ -0,0 +1,76 @@ +<% include("/elements/header.html",'Batch Customer Import') %> + +
+ +Import a CSV file containing 810 data. +

+ + + +Extended file format is CSV, with the +

+ +<%$req%> Required fields +

+ +[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. +

+ +[2] username and _password are required if pkgpart is specified. +

+ +<% &ntable("#cccccc") %> + +<% include('/elements/tr-select-agent.html', '', #$agentnum, + 'label' => "Agent", + 'empty_label' => 'Select agent', + ) +%> + + + Format + + + + + + + CSV filename + + +% #include('/elements/tr-select-part_referral.html') +% + + + + + +

+ + +
+ +<% include('/elements/footer.html') %> + +<%once> +my $req = qq!*!; + diff --git a/httemplate/misc/usage_elec_prefilled_input.cgi b/httemplate/misc/usage_elec_prefilled_input.cgi new file mode 100755 index 000000000..43b45d0d0 --- /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!Error: !, $cgi->param('error'), +% "" +% if $cgi->param('error'); +% +%print qq!
!; +% +%# 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 +%# +%# + +% +% # -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 "" +% . join("\n", map("", @field_descriptions)) +% . "\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 %>
+% push(@usage_ele, $usage->$field); +% } +% } +% +% print "" +% . join("\n", map("", @usage_ele)) +% . "\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 !!; +% 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 ! +% +% !; +% } +% else { +% print qq ! +% +% !; +% } +% } +% print "\n"; +% + +
" . $_ . "
" . $_ . "
+% +% +% +%
+% +% +% Ignore

+%

+%
+% +%

+%print "
measured demand = ",$h_measured_demand,"\n
" if ($debug); +% + + + +% +% print qq ! +%

+% prev_date, curr_date - +% 8 digit in format of yyyymmdd (y-year m-month d-date)
+% 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.
+% 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
+% !; +% +
+ + + diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html index a340b7fc9..7522b8e78 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 ab76c3bdc..0e24e6aef 100644 --- a/httemplate/pref/pref.html +++ b/httemplate/pref/pref.html @@ -53,6 +53,22 @@ Interface + + + Dashboard customer history: + + + + Disable HTML editor for customer notes: diff --git a/httemplate/view/cust_bill-pdf.cgi b/httemplate/view/cust_bill-pdf.cgi index 51e47e00d..c1639fd48 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 ce8d96a95..4e97bb54c 100755 --- a/httemplate/view/cust_bill.cgi +++ b/httemplate/view/cust_bill.cgi @@ -78,6 +78,15 @@ View typeset invoice PDF

+% if ( $conf->exists('svc_elec_features') ) { +% my $reclink = "invnum=$invnum"; +% $reclink .= ';template='. uri_escape($template) if $template; + ">View Record
+ ">View Record Ignoring DUE DATE +

+ +% } + % } % my $br = 0; diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi index 77679d81c..cee026266 100644 --- a/httemplate/view/svc_external.cgi +++ b/httemplate/view/svc_external.cgi @@ -23,6 +23,10 @@ +
+% if ($conf->exists('svc_elec_features')) { +Manually manipulate electric usage
+% }
<% joblisting({'svcnum'=>$svcnum}, 1) %> <% include('/elements/footer.html') %>