diff options
Diffstat (limited to 'FS')
| -rw-r--r-- | FS/FS/Conf.pm | 17 | ||||
| -rw-r--r-- | FS/FS/Mason.pm | 6 | ||||
| -rw-r--r-- | FS/FS/Schema.pm | 159 | ||||
| -rw-r--r-- | FS/FS/cust_bill.pm | 192 | ||||
| -rw-r--r-- | FS/FS/cust_bill_pkg.pm | 32 | ||||
| -rw-r--r-- | FS/FS/cust_bill_pkg_detail.pm | 68 | ||||
| -rw-r--r-- | FS/FS/cust_main.pm | 780 | ||||
| -rwxr-xr-x | FS/FS/elec_general.pm | 121 | ||||
| -rw-r--r-- | FS/FS/part_pkg.pm | 13 | ||||
| -rwxr-xr-x | FS/FS/part_pkg/business_elec_generic.pm | 96 | ||||
| -rwxr-xr-x | FS/FS/part_pkg/energy_base_discount_500kwh.pm | 150 | ||||
| -rwxr-xr-x | FS/FS/part_pkg/energy_base_discount_tiers.pm | 160 | ||||
| -rwxr-xr-x | FS/FS/part_pkg/residential_elec_generic.pm | 94 | ||||
| -rwxr-xr-x | FS/FS/part_pkg/residential_elec_generic_var.pm | 113 | ||||
| -rwxr-xr-x | FS/FS/svc_elec.pm | 138 | ||||
| -rwxr-xr-x | FS/FS/transaction810.pm | 307 | ||||
| -rwxr-xr-x | FS/FS/transaction867.pm | 300 | ||||
| -rwxr-xr-x | FS/FS/usage_elec.pm | 647 | ||||
| -rwxr-xr-x | FS/FS/usage_elec_transaction867.pm | 124 | ||||
| -rw-r--r-- | FS/MANIFEST | 13 | ||||
| -rwxr-xr-x | FS/t/elec_general.t | 5 | ||||
| -rwxr-xr-x | FS/t/svc_elec.t | 5 | ||||
| -rwxr-xr-x | FS/t/transaction810.t | 5 | ||||
| -rwxr-xr-x | FS/t/transaction867.t | 5 | ||||
| -rwxr-xr-x | FS/t/usage_elec.t | 5 | ||||
| -rwxr-xr-x | FS/t/usage_elec_transaction867.t | 5 | 
26 files changed, 3531 insertions, 29 deletions
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 <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Typeset_.28LaTeX.29_invoice_templates">billing documentation</a> for details.', +    'type'        => 'textarea', +  }, + +  {      'key'         => 'invoice_email_pdf',      'section'     => 'invoicing',      'description' => 'Send PDF invoice as an attachment to emailed invoices.  By default, includes the plain text invoice as the email body, unless invoice_email_pdf_note is set.', @@ -1181,7 +1188,7 @@ and customer address. Include units.',      'section'     => 'invoicing',      'description' => 'Optional default invoice term, used to calculate a due date printed on invoices.',      'type'        => 'select', -    'select_enum' => [ '', 'Payable upon receipt', 'Net 0', 'Net 10', 'Net 15', 'Net 20', 'Net 30', 'Net 45', 'Net 60' ], +    'select_enum' => [ '', 'Payable upon receipt', 'Net 0', 'Net 10', 'Net 15', 'Net 16', 'Net 20', 'Net 30', 'Net 45', 'Net 60' ],    },    {  @@ -3885,6 +3892,14 @@ and customer address. Include units.',    },    { +    'key'         => 'svc_elec_features', +    'section'     => '', +    'description' => 'Enable electrical billing features', +    'type'        => 'select', +    'type'        => 'checkbox', +  }, + +  {      'key'         => 'maestro-status_test',      'section'     => 'UI',      'description' => 'Display a link to the maestro status test page on the customer view page', diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 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.<br>$inpstr"  +                                                         if ($cn !~ /^\d{9}$/);  + +        return "error: custnum '$cn' must start with a 9 or 1.<br>$inpstr"  +                                                         if ($cn !~ /^(1|9)/);  +  +        $cust_main{$field} = shift @columns; +      } +      elsif ( $field =~ /^(name|ship_name)$/ ) { +        my ($last,$first) = split (/,/,$columns[0]); +        $last =~ s/\s*$//;  # remove trailing spaces +        $first =~ s/^\s*//; # remove leading spaces + +        if ($field eq 'name') { +          $cust_main{'last'} = $last; +          $cust_main{'first'} = $first; +        } +        else { +          $cust_main{'ship_last'} = $last; +          $cust_main{'ship_first'} = $first; +        } + +        shift @columns; + +      } +      elsif ($field =~ /^(citystate|ship_citystate)$/) { + +        #if ( $columns[0] =~ /(.*?)\s([a-zA-Z]{2})$/ ) { #use for any state +        if ( $columns[0] =~ /(.*)\s(TX)$/i ) { # TX only +          my ($city,$state) = (uc $1,uc $2); +          $city =~ s/\s*$//;  # remove trailing spaces + +          if ($field eq 'citystate') { +            $cust_main{'city'} = $city; +            $cust_main{'state'} = $state; +          } +          else { +            $cust_main{'ship_city'} = $city; +            $cust_main{'ship_state'} = $state; +          } +        } +        else { +          return "error: Field city_state '".$columns[0]."',don't match" +                ." format 'city state'. I.E. SUGAR LAND TX" +                ."<br>$inpstr"; +        } + +        shift @columns; +      +      } +      elsif ( $field =~ /^(zip|ship_zip)$/ ) { +        if ( $columns[0] =~ /^(\d{5}|\d{9})$/ ) { +          my $zipcode = $1; +          # cludge.  Because the system is not accepting a straight +          # 9 digit zipcode.  Need to have in format ddddd-dddd +          $zipcode =~ s/^(\d{5})(\d{4})$/$1\-$2/; +          $cust_main{$field} = $zipcode; +        } +        else { +          return "error: Zip code '".$columns[0]."' need to be in the format " +                ."of 5 digit or 9 digit. I.E. 75227 or 752271212" +                ."<br>$inpstr"; +        } +        shift @columns; +      } +      elsif ( $field eq 'ss' ) { +        if ($columns[0]) { #ignore if blak +          if ( $columns[0] =~ /^\d{9}$/ ) { +            # verify social security number format +            $cust_main{$field} = $columns[0]; +          } +          else { +            return "error: Social Security number ".$columns[0]."' need to be in"  +                  ." the format of 9 digit. No dash or non digit character are" +                  ." allowed.  I.E. 512342898" +                  ."<br>$inpstr"; +          } +        } +        shift @columns; +      } +      elsif ( $field =~ /^(daytime|night)$/ ) { +        if ( $columns[0] ) { #ignore if blank +          if ( $columns[0] =~ /^(\d{3})(\d{3})(\d{4})$/ ) { +            # duplicate the ph # to service address too +            $cust_main{$field} = $cust_main{"ship_$field"}= "$1\-$2\-$3"; +          } +          else { +            return "error: Phone number ".$columns[0]."' need to be in the format " +                  ."of 10 digit. No dash or non digit character are allowed." +                  ." I.E. 8179072171" +                  ."<br>$inpstr"; +          } +        } +        shift @columns; + +      } +      elsif ( $field eq 'newcustdate' ) { + +        # string format coming in is in the form of m/d/yyyy +        if ($columns[0] =~ /^\d{1,2}\/\d{1,2}\/\d{4}$/) { +          my($month, $day, $year) = split(/\//,$columns[0]); + +          # pad month and day with a '0' if they are single digit +          $month =~ s/^(\d)$/0$1/; +          $day   =~ s/^(\d)$/0$1/; +  +          my $date = str2time("${year}${month}${day}"); +         +          $cust_main{'signupdate'} = $date; +        } +        else { +          return "error: Time '".$columns[0]."' format is not correct." +                ." Accepted format is m/d/yyyy i.e. 3/5/2002." +                ."<br>$inpstr"; +        } +        shift @columns; + +      } +      else { +        $cust_main{$field} = shift @columns;  +      } + +    } #foreach + +    my $cust_main = new FS::cust_main ( \%cust_main ); + +    use Tie::RefHash; +    tie my %hash, 'Tie::RefHash'; #this part is important + +    my $invoicing_list = []; +    my $error = $cust_main->insert( \%hash, $invoicing_list ); + +    if ( $error ) { +      $dbh->rollback if $oldAutoCommit; +      return "can't insert customer for $line: $error"; +    } + +    if ( $format eq 'simple' ) { + +      #false laziness w/bill.cgi +      $error = $cust_main->bill( 'time' => $billtime ); +      if ( $error ) { +        $dbh->rollback if $oldAutoCommit; +        return "can't bill customer for $line: $error"; +      } +   +      $cust_main->apply_payments; +      $cust_main->apply_credits; +   +      $error = $cust_main->collect(); +      if ( $error ) { +        $dbh->rollback if $oldAutoCommit; +        return "can't collect customer for $line: $error"; +      } + +    } + +    $imported++; +  } + +  $dbh->commit or die $dbh->errstr if $oldAutoCommit; + +  return "Empty file!" unless $imported; + +  ''; #no error + +} # sub batch_import_onp + + +# This import script was written to import and process 810 & 867 edi +# data for customer +# coder: Cal Tran +# dob: 3/11/08 + +=item batch_edidata_onp + +=cut + +sub batch_edidata_onp { +  my $param = shift; +  #warn join('-',keys %$param); +  my $fh = $param->{filehandle}; +  my $agentnum = $param->{agentnum}; + +  my $refnum = $param->{refnum}; +  my $pkgpart = $param->{pkgpart}; + +  my $debug = 1; +  #my @fields = @{$param->{fields}}; +  my $format = $param->{'format'}; +  my (@fields, @incoming_fields); +  my $payby; +  if ( $format eq 'simple' ) { +    @fields = qw( cust_pkg.setup dayphone first last +                  address1 address2 city state zip comments ); +    $payby = 'BILL'; +  }  +  elsif ( $format eq 'extended' ) { + +  } +  else { +    die "unknown format $format"; +  } + +  eval "use Text::CSV_XS;"; +  die $@ if $@; + +  my $csv = new Text::CSV_XS; +  #warn $csv; +  #warn $fh; + +  my $imported = 0; +  #my $columns; + +  local $SIG{HUP} = 'IGNORE'; +  local $SIG{INT} = 'IGNORE'; +  local $SIG{QUIT} = 'IGNORE'; +  local $SIG{TERM} = 'IGNORE'; +  local $SIG{TSTP} = 'IGNORE'; +  local $SIG{PIPE} = 'IGNORE'; + +  my $dbh = dbh; +  my $return_msg; +   +  #while ( $columns = $csv->getline($fh) ) { +  my $line; +  while ( defined($line=<$fh>) ) { + +    $csv->parse($line) or do { +      return "ERROR can't parse: ". $csv->error_input(); +    }; + +    my @columns = $csv->fields(); +    my $inpstr = $debug ? join ('|',@columns) : ''; +    #warn join('-',@columns); + +    # getting rid of all leading and trailing spaces +    foreach my $value (@columns) { +      $value =~ s/^\s*(.*?)\s*$/$1/; +    } + +    my $esiid = $columns[3]; + +    ### check for matching of 810 usage and 867 usage +    my $usage_match_810_867; +    if ($columns[11] == $columns[31]) { +      $usage_match_810_867 = 0; +    }  +    else { +      $usage_match_810_867 = 'FALSE'; +    }; + + +    my @svc_external = qsearch ( 'svc_external', { 'id'    => $esiid } ); + +    return "ERROR fail: No one own ESIID $esiid!" unless @svc_external; + +    my $cust_main; +    my $cust_pkg; + +    foreach my $svcexternal (@svc_external) { +      my $cust_svc  = qsearchs ( 'cust_svc', { 'svcnum'    => $svcexternal->svcnum} ); +      unless ($cust_svc) { +        return "ERROR fail1"; +      } + +      $cust_pkg  = qsearchs ( 'cust_pkg', { 'pkgnum'    => $cust_svc->pkgnum} ); +      unless ($cust_pkg) { +        return "ERROR fail2"; +      } + +      # don't process any package unless it is active +      next if ($cust_pkg->status() ne "active"); +        +      $cust_main  = qsearchs ( 'cust_main', { 'custnum'    => $cust_pkg->custnum} ); +      unless ($cust_main) { +        return "ERROR fail3"; +        #return "can't insert customer for $line: $error"; +      } + +      # don't process any customer that is not active +      next if ($cust_main->status() ne "active"); + +      my $svc_num = $svcexternal->svcnum; +      my $firstname = $cust_main->first; +      my $lastname = $cust_main->last; +      my $custnum = $cust_main->custnum; +      my $balance = $cust_main->balance; +      my $lastbilled = time2str('%D',$cust_pkg->get('last_bill')); + +      # note - the empty column that is after $lastbilled is later used tostore +      # the last reading from usage_elec +      my $easy_view = join(',',$usage_match_810_867, $firstname, $lastname, $custnum, +                               $svc_num,$balance, $lastbilled,'--', +                               '**', +         # 13-start date 14-end date 28-prev read 29-curr read +                               $columns[13], $columns[14], $columns[28],  +         # 29-curr read 6-tdsp +                               $columns[29], $columns[6], +         # 30 - meter multiplier 31-867 usage 21-measured demand +                               $columns[30], $columns[31], $columns[21],  +         # 20 - billed demand +                               $columns[20], '','','', +                               '**', +                               @columns +         ); + +      if ($return_msg) { +        $return_msg .= "\n$easy_view"; +      } +      else { +        $return_msg = "$easy_view"; +      } + +    } #foreach svc_external + +  } + +  return($return_msg);  + +  ''; #no error + +} # sub batch_edidata_onp + +sub insert_test_value{ +  my $time = time; + +	 my $oldAutoCommit = $FS::UID::AutoCommit; +  local $FS::UID::AutoCommit = 0; +  my $dbh = dbh; +	my $transaction810 = new FS::usage_elec({ +		'prev_date'=>'1', +		'curr_date'=>'1', +		'prev_read'=>'1', +		'curr_read'=>'1', +		'tdsp'=>'1', +		'svcnum'=>'10', +		'meter_multiplier'=>'1', +		'measured_demand'=>'1', +		'billed_demand'=>'1', + +		 +	}); + +    my $error=$transaction810->insert; +	if ( $error ) { +      $dbh->rollback if $oldAutoCommit; +      return "error applying prepaid card (transaction rolled back): $error"; +       +    } +} +  sub _upgrade_data { #class method    my ($class, %opts) = @_; diff --git a/FS/FS/elec_general.pm b/FS/FS/elec_general.pm new file mode 100755 index 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'elec_general'; } + +=item insert + +Adds this record to the database.  If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database.  If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example.  If there is +an error, returns the error, otherwise returns false.  Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { +  my $self = shift; + +  my $error =  +    $self->ut_numbern('id') +    || $self->ut_number('esiid') +  ; +  return $error if $error; + +  $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'svc_elec'; } + +=item insert + +Adds this record to the database.  If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database.  If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example.  If there is +an error, returns the error, otherwise returns false.  Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { +  my $self = shift; + +  my $error =  +    $self->ut_numbern('svcnum') +    || $self->ut_number('id') +    || $self->ut_number('esiid') +    || $self->ut_text('countrycode') +    || $self->ut_text('phonenum') +    || $self->ut_textn('pin') +  ; +  return $error if $error; + +  $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; diff --git a/FS/FS/transaction810.pm b/FS/FS/transaction810.pm new file mode 100755 index 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'transaction810'; } + +=item insert + +Adds this record to the database.  If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database.  If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example.  If there is +an error, returns the error, otherwise returns false.  Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { +  my $self = shift; + +  my $error =  +    $self->ut_numbern('id') +    || $self->ut_number('tdsp_duns') +    || $self->ut_number('inv_num') +    || $self->ut_textn('ref_identification') +    || $self->ut_number('esiid') +    || $self->ut_number('tdsp') +    || $self->ut_number('due_date') +    || $self->ut_number('inv_date') +    || $self->ut_float('usage_kwatts') +    || $self->ut_number('srvc_from_date') +    || $self->ut_number('srvc_to_date') +    || $self->ut_number('puct_fund') +    || $self->ut_float('billed_demand') +    || $self->ut_floatn('measured_demand') +    || $self->ut_text('bill_status') +    || $self->ut_numbern('type_of_bill') +    || $self->ut_numbern('ack_997') +    || $self->ut_numbern('processed') +  ; +  return $error if $error; + +  $self->SUPER::check; +} + +sub testing { +  my $param = shift; + +  my @usages = qsearch ( 'usage_elec' ); + +  foreach my $usage (@usages) { +    print "meter number: " . $usage->meter_number . "\n"; +  } + +  return "Successful sub read\n"; +} + +=item batch_810data_import + + +Importing a CVS file with the following column: +  duns inv_num 867_usage esiid tdsp due_date inv_date usage_kwatts  +  srvc_from_date srvc_to_date puct_fund billed_demand  +  measured_demand bill_status type_of_bill 997_ack + +=cut + +#@EXPORT_OK=qw(batch_810data_import); +sub batch_810data_import { +  #my $param = shift; +  my ($fh,$format) = @_; + +#  print "\n\n****************** the cvs file\n\n"; +#  print (<$fh>); +#  print ("\n$format\n"); +#  return "done\n"; + +  #my $fh = $param->{filehandle}; +  #my $format = $param->{'format'}; +  my $error; +  my $debug = 0; +   +  my @fields; +  if ( $format eq 'extended' ) { +    @fields = qw(  +		  tdsp_duns inv_num ref_identification esiid tdsp due_date +		  inv_date usage_kwatts srvc_from_date srvc_to_date +		  puct_fund billed_demand measured_demand bill_status +		  type_of_bill +                ); +  } else { +    die "unknown format $format"; +  } + +  eval "use Text::CSV_XS;"; +  die $@ if $@; + +  my $csv = new Text::CSV_XS; +  #warn $csv; +  #warn $fh; + +  my $imported = 0; +  #my $columns; + +  local $SIG{HUP} = 'IGNORE'; +  local $SIG{INT} = 'IGNORE'; +  local $SIG{QUIT} = 'IGNORE'; +  local $SIG{TERM} = 'IGNORE'; +  local $SIG{TSTP} = 'IGNORE'; +  local $SIG{PIPE} = 'IGNORE'; + +  my $oldAutoCommit = $FS::UID::AutoCommit; +  local $FS::UID::AutoCommit = 0; +  my $dbh = dbh; +   +  #while ( $columns = $csv->getline($fh) ) { +  my $line; +  while ( defined($line=<$fh>) ) { + +    $csv->parse($line) or do { +      $dbh->rollback if $oldAutoCommit; +      return "can't parse: ". $csv->error_input(); +    }; + +    my @columns = $csv->fields(); +    #warn join('-',@columns); + +    # this hash will hold each CVS line +    my %transaction810_data; + +    my $billtime = time; +#    my %cust_pkg = ( pkgpart => $pkgpart ); +    my %svc_acct = (); +    foreach my $field ( @fields ) { +	# -cal  this section is ignored by the 810 import  +	$transaction810_data{$field} = shift @columns; +    } + +    # initialize the column 'ack_997' to 0 (not yet sent ack) +    $transaction810_data{'ack_997'} = 0; + +    # initialize the column 'processed' to 0 (not process yet) +    $transaction810_data{'processed'} = 0; + +   print Dumper(\%transaction810_data) if $debug; +   #return ("done\n"); + +    ### check to see if the invoice is already in transaction810 table +    # if so then print a warning +    my $inv_num = $transaction810_data{'inv_num'}; +    my $search_res = qsearchs ( 'transaction810', +                                {'inv_num' => $inv_num} +                              ); +    if ( $search_res ) { +      ### +      #place some code to fix this problem here +      # +      print "$line\n"; +      print "OOps! a transaction with invoice number " +           ."$transaction810_data{'inv_num'}\n" +           ."\t is in the table already!!\n"; +    } +    else { +      my $transaction810_obj = new FS::transaction810( \%transaction810_data ); +      $error = $transaction810_obj->insert; +      if ( $error ) { +        $dbh->rollback if $oldAutoCommit; +        return "can't bill customer for $line: $error"; +      } +      $imported++; +    } +  } + +  $dbh->commit or die $dbh->errstr if $oldAutoCommit; + +  return "Empty file!" unless $imported; + +  ''; #no error + +} + + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/transaction867.pm b/FS/FS/transaction867.pm new file mode 100755 index 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'transaction867'; } + +=item insert + +Adds this record to the database.  If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database.  If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example.  If there is +an error, returns the error, otherwise returns false.  Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { +  my $self = shift; + +  my $error =  +    $self->ut_numbern('id') +    || $self->ut_number('tdsp_duns') +    || $self->ut_text('ref_identification') +    || $self->ut_text('esiid') +    || $self->ut_number('trans_creation_date') +    || $self->ut_text('meter_no') +    || $self->ut_number('srvc_period_start_date') +    || $self->ut_number('srvc_period_end_date') +    || $self->ut_float('prev_read_kwatts') +    || $self->ut_float('curr_read_kwatts') +    || $self->ut_float('meter_multiplier') +    || $self->ut_float('usage_kwatts') +    || $self->ut_floatn('measured_demand') +    || $self->ut_number('ack_997') +    || $self->ut_number('processed') +  ; +  return $error if $error; + +  $self->SUPER::check; +} + + + + +=item batch_867data_import + + +Importing a CVS file with the following column: +  867_usage esiid date meter srvc_from_date srvc_to_date previous_read_kwatts  +  current_read_kwatts mult usage_kwatts measure_demand 997_ack + +=cut + +#@EXPORT_OK=qw(batch_867data_import); +sub batch_867data_import { +  #my $param = shift; +  my ($fh,$format) = @_; + +#  print "\n\n****************** the cvs file\n\n"; +#  print (<$fh>); +#  print ("\n$format\n"); +#  return "done\n"; + +  #my $fh = $param->{filehandle}; +  #my $format = $param->{'format'}; +  my $error; +  my $debug = 0; +   +  my @fields; +  if ( $format eq 'extended' ) { +    @fields = qw(  +		  tdsp_duns ref_identification esiid trans_creation_date +                  meter_no srvc_period_start_date srvc_period_end_date +                  prev_read_kwatts curr_read_kwatts meter_multiplier +                  usage_kwatts measured_demand ack_997 processed +                ); +  } else { +    die "unknown format $format"; +  } + +  eval "use Text::CSV_XS;"; +  die $@ if $@; + +  my $csv = new Text::CSV_XS; +  #warn $csv; +  #warn $fh; + +  my $imported = 0; +  #my $columns; + +  local $SIG{HUP} = 'IGNORE'; +  local $SIG{INT} = 'IGNORE'; +  local $SIG{QUIT} = 'IGNORE'; +  local $SIG{TERM} = 'IGNORE'; +  local $SIG{TSTP} = 'IGNORE'; +  local $SIG{PIPE} = 'IGNORE'; + +  my $oldAutoCommit = $FS::UID::AutoCommit; +  local $FS::UID::AutoCommit = 0; +  my $dbh = dbh; +   +  #while ( $columns = $csv->getline($fh) ) { +  my $line; +  while ( defined($line=<$fh>) ) { + +    $csv->parse($line) or do { +      $dbh->rollback if $oldAutoCommit; +      return "can't parse: ". $csv->error_input(); +    }; + +    my @columns = $csv->fields(); +    #warn join('-',@columns); + +    # this hash will hold each CVS line +    my %transaction867_data; + +    my $billtime = time; +#    my %cust_pkg = ( pkgpart => $pkgpart ); +    my %svc_acct = (); +    foreach my $field ( @fields ) { +	# -cal  this section is ignored by the 867 import  +	$transaction867_data{$field} = shift @columns; +    } + +    # make sure to set the 'ack_997' column +    $transaction867_data{'ack_997'} = 0; + +    # make sure to set the 'processed' column +    $transaction867_data{'processed'} = 0; + +    print Dumper(\%transaction867_data) if $debug; + +    ### Check to see if the invoice is already in transaction810 table +    # if so then print a warning + +    my $ref_identification = $transaction867_data{'ref_identification'}; +    my $search_res = qsearchs ( 'transaction867', +                                {'ref_identification' => $ref_identification} +                              ); + +    if ($search_res) { + +      ###  +      # place some code here to fix the problme of trying to insert +      # data that have the same references identification number + +      print "$line\n"; +      print "OOps! a transaction with the same references identification" +           ." number $ref_identification\n" +           ."\tis in the transaction867 table already!!\n"; + +    } +    else { + +      my $transaction867_obj = new FS::transaction867( \%transaction867_data ); +      $error = $transaction867_obj->insert; +      if ( $error ) { +        $dbh->rollback if $oldAutoCommit; +        return "can't bill customer for $line: $error"; +      } + +      $imported++; + +    } + +  } #end while + +  $dbh->commit or die $dbh->errstr if $oldAutoCommit; + +  return "Empty file!" unless $imported; + +  ''; #no error + +} + + + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/usage_elec.pm b/FS/FS/usage_elec.pm new file mode 100755 index 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'usage_elec'; } + +=item insert + +Adds this record to the database.  If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database.  If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example.  If there is +an error, returns the error, otherwise returns false.  Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { +  my $self = shift; + +  my $error =  +    $self->ut_numbern('id') +    || $self->ut_numbern('prev_date') +    || $self->ut_numbern('curr_date') +    || $self->ut_number('prev_read') +    || $self->ut_number('curr_read') +    || $self->ut_money('tdsp') +    || $self->ut_number('svcnum') +    || $self->ut_numbern('_date') +    || $self->ut_float('meter_multiplier') +    || $self->ut_numbern('demand_measure') +    || $self->ut_numbern('demand_bill') +  ; +  return $error if $error; + +  $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut +sub most_current_date { + # my $self = shift; +  my $cust_nr=shift; +  my @custs = qsearch('usage_elec',{ 'cust_nr' => $cust_nr}); + +  my $most_current_date  = 0; +   +  if (@custs) { +     +    foreach my $cust (@custs) { +       if ($cust->curr_date > $most_current_date){ +		$most_current_date = $cust;    +	} +    } +  } +   +  return $most_current_date; + +} + +sub getUsage{ +        my $self = shift; +	return $self->total_usage; +} +#sub getUsage{ +#	my $self = shift; +#    my $prev_read=$self->prev_read; +#	my $curr_read=$self->curr_read; +#        my $usage; +#	if ($prev_read<=$curr_read) { +#		$usage= ($curr_read-$prev_read); +#	} +#	else{ +#		$usage=(($curr_read+10**max(length($prev_read),length($curr_read)))-$prev_read); +#	} +#	return $usage*$self->meter_multiplier; +#} + +sub getNumberOfDays { +  my $self = shift; +  return Date::Calc::Delta_Days( time2str('%Y', $self->prev_date),  +                                 time2str('%L', $self->prev_date), +                                 time2str('%e', $self->prev_date),  +                                 time2str('%Y', $self->curr_date), +			         time2str('%L', $self->curr_date),  +                                 time2str('%e', $self->curr_date) +                               ); +} + + +### insert into table +# +sub insert_usage { +  my $self = shift; +  +  my $debug = 0; +  my $error; +   +  local $SIG{HUP} = 'IGNORE'; +  local $SIG{INT} = 'IGNORE'; +  local $SIG{QUIT} = 'IGNORE'; +  local $SIG{TERM} = 'IGNORE'; +  local $SIG{TSTP} = 'IGNORE'; +  local $SIG{PIPE} = 'IGNORE'; +  +  my $oldAutoCommit = $FS::UID::AutoCommit; +  local $FS::UID::AutoCommit = 0; +  my $dbh = dbh; + +  $error = $self->check; +  return $error if $error; + +  $error = $self->SUPER::insert; +  if ( $error ) { +    $dbh->rollback if $oldAutoCommit; +    my $msg = "error: Can't insert data into usage_elec : $error\n" +             .Dumper($self); +    return $msg; +  } +  +  $dbh->commit or die $dbh->errstr if $oldAutoCommit; + +  return; +} + +### Take in a time and convert it to time string to be entered into usage_elec +### the function use is str2time from module "HTTP::Date qw( str2time )" +sub to_usage_elec_time { +  my ($time) = shift; + +  ### becareful using time2str, year allows are 1970-jan2038 + +  return str2time($time); +} + + +# Get the past 10 usage for a particular svcnum and return the object +# return: +#  array of usages object +#  undef otherwise +# +# + +sub query_usage { +  my ($svcnum, $how_many) = @_; + +  #$how_many = 10 unless $how_many; # default to 10 usages + +#  my @usages  = qsearch (  +#                          'usage_elec',  +#                          { +# +#                           'svcnum' => $svcnum, +#                           # sort in DESCending order so it easier to splice +#                           # the array in the next step +#                           'extra_sql' => 'ORDER BY _date DESC' +#                          } +#                        ); + +  my @usages = qsearch ( { +                          'table'   => 'usage_elec', +                          'hashref' => { 'svcnum' => $svcnum }, +                          'extra_sql' => 'ORDER BY _date DESC' +                         } ); + +  # shrink the array to $how_many index if it over the requested number +  $#usages = $how_many - 1 if ( @usages && $how_many && (@usages > $how_many) ); +     + +  if (@usages) { +    # since we query the usage by DESCending order, it a good idea to put it  +    # in ascending order before a return. +    @usages = reverse @usages; +    return @usages; +  } + +  return; +} + + +# sub routine that go through the transaction810 and transaction867 table +# to put data into usage_elec table +# +# +# some note +# to input data into usage_elect, all condition below must be meet +# 1. there is unprocess data from transaction810 table +# 2. there is unprocess data from transaction867 table +# 3. the unprocess data from transaction867 match transaction810 +#    data. + +sub edi_to_usage { +  my $self = shift; + +  my $debug = 1; +  #my @invoices_to_generate; # store usage_elec svcnum  + +  # Only send data to usage_elec if a transactin from 810 & 867 match up +  # +  +  # first thing first.  Let get all edi from transaction_810 table that haven't +  # been process +  my @edi_810_processeds = qsearch (  +                            'transaction810',  +                            {'processed' => '0'} +                          ); + +  unless (@edi_810_processeds) { +    return "There were no un-process 810 to input into usage_elec.\n" +          ."Run again when there is 810 data to process\n"; +  } + +  # second, let get all edi from transaction_867 table that haven't been  +  # process +  my @edi_867_processeds = qsearch (  +                            'transaction867',  +                            {'processed' => '0'} +                          ); + +  unless (@edi_867_processeds) { +    return "There were no un-process 867 to match up with 810 data.\n" +          ."Run again when there is 867 data to process\n"; +  } + +  # third, match up the 810 and 867 data.  Those data that match up, goes +  # into usage_elec table. + +  ### for efficientcy we will use the smaller list to traverse +  if (@edi_810_processeds < @edi_867_processeds) { +     +    print "debug: using 810\n" if $debug; + +    foreach my $edi_810 (@edi_810_processeds) { +      # find matching 867 +      my $ref_identification_810 = $edi_810->ref_identification; +      my $srv_from_810 = $edi_810->srvc_from_date; +      my $srv_to_810 = $edi_810->srvc_to_date; +      ### search for the edi that match exactly with the 810 +      my $edi_867 = qsearchs ( 'transaction867', +                               { 'ref_identification' => $ref_identification_810, +                                 'srvc_from_date'     => $srv_from_810, +                                 'srvc_to_date'       => $srv_to_810, +                               } +                            ); +      if ($edi_867) { +        ### we have a match, extract the data and put into usage +        my $usage_elec_obj = extract_data_to_usage_elec ($edi_810, $edi_867); +        if ($usage_elec_obj) { + +          ### mark the 810 and 867 as already process +          $edi_810->setfield('processed',1); +          $edi_867->setfield('processed',1); + +          ### go ahead and billed  +          my $rtnval = billing_call($usage_elec_obj); +          if ($rtnval) { +            print "Oh! Oh!.. unable to bill svcnum: $usage_elec_obj->svcnum\n"; +            print $rtnval; +            $edi_810->setfield('processed',0); +            $edi_867->setfield('processed',0); +            $usage_elec_obj->delete; +            return; +          } + +        } +        else { +          print "RED ALERT.. something went wrong when inserting data\n" +               ."into usage_elec (810)\n"; +          print "ref_identification of 810 : " . $edi_867->ref_identification +               ."\n"; +          return; +        } + +      } +    } +     +  } +  else { + +    print "debug: using 867\n" if $debug; + +    foreach my $edi_867 (@edi_867_processeds) { +      # find matching 810 +      my $ref_identification_867 = $edi_867->ref_identification; +      my $srv_from_867 = $edi_867->srvc_period_start_date; +      my $srv_to_867 = $edi_867->srvc_period_end_date; +      print "(debug) ref_identification: $ref_identification_867\n" if $debug; +      ### search for the edi that match exactly with the 867 +      my $edi_810 = qsearchs ( 'transaction810', +                               { 'ref_identification' => $ref_identification_867, +                                 'srvc_from_date'     => $srv_from_867, +                                 'srvc_to_date'       => $srv_to_867, +                               } +                             ); +      if ($edi_810) { + +        print "(debug) found an 810 that match the 867: esiid " +             .$edi_810->esiid."\n" if $debug; + +        ### we have a match, extract the data and put into usage +        my $usage_elec_obj = extract_data_to_usage_elec($edi_810, $edi_867); +        if ($usage_elec_obj) { + +          ### mark the 810 and 867 as already process +          my $edi_810_new = new FS::transaction810( { $edi_810->hash } ); +          $edi_810_new->setfield('processed',1); +          my $error = $edi_810_new->replace($edi_810); +          if ($error) { +            print "there is an error changing column 'processed' of transaction810 table\n"; +            print "error: $error\n"; +          } + +          my $edi_867_new = new FS::transaction867( { $edi_867->hash } ); +          $edi_867->setfield('processed',1); +          $error = $edi_867_new->replace($edi_867); +          if ($error) { +            print "there is an error changing column 'processed' of transaction867 table\n"; +            print "error: $error\n"; +          } + +          ### go ahead and billed  +          my $rtnval = billing_call($usage_elec_obj); +          if ($rtnval) { +            print "Oh! Oh!.. unable to bill svcnum: $usage_elec_obj->svcnum\n"; +            print "$rtnval"; +            $edi_810->setfield('processed',0); +            $edi_867->setfield('processed',0); +            $usage_elec_obj->delete; +            return; +          } +        } +        else { +          print "RED ALERT.. something went wrong when inserting data\n" +               ."into usage_elec (810)\n"; +          print "ref_identification of 810 : " . $edi_810->ref_identification +               ."\n"; +          #return; +        } +      } +    } + +  } + + +} + +# This subroutine does the physical adding of data into usage_elec +# using the transaction810 and transaction867 table + +sub extract_data_to_usage_elec { +  my ($edi_810, $edi_867) = @_; + +  ### variables declaration +  ### following decl are column of usage_elec +  my ($prev_date, $curr_date, $prev_read, $curr_read, $tdsp, $svcnum, $_date, +      $meter_multiplier, $total_usage, $measured_demand, $billed_demand, +      $meter_number); + +  local $SIG{HUP} = 'IGNORE'; +  local $SIG{INT} = 'IGNORE'; +  local $SIG{QUIT} = 'IGNORE'; +  local $SIG{TERM} = 'IGNORE'; +  local $SIG{TSTP} = 'IGNORE'; +  local $SIG{PIPE} = 'IGNORE'; + +  my $oldAutoCommit = $FS::UID::AutoCommit; +  local $FS::UID::AutoCommit = 0; +  my $dbh = dbh; + +  ### this message will print in the year 2038 because their is a limitation +  #   with str2time ( cpan.org package Icwa-1.0.0.tar.gz ) +  if ( int(time2str('%Y',time)) > 2037 ) { +    print "Bug: Try to use function 'time2str' has generate an error because" +         ."\n\tit can't handle year greter than 2037.\n"; +    return; +  }    + +  # data from 867 +  $prev_date = str2time($edi_867->srvc_period_start_date); +  $curr_date = str2time($edi_867->srvc_period_end_date); +  $prev_read = $edi_867->prev_read_kwatts; +  $curr_read = $edi_867->curr_read_kwatts; +  $meter_multiplier = $edi_867->meter_multiplier; +  $total_usage = $edi_867->usage_kwatts; +  $measured_demand = $edi_867->measured_demand; +  $meter_number = $edi_867->meter_no; + +  # data from 810 +  $tdsp = sprintf('%.2f',$edi_810->tdsp/100); +  $billed_demand = $edi_810->billed_demand; + +  +  ### obtain the svcnum +  my $esiid = $edi_810->esiid; +  my $svc_obj = qsearchs (  'svc_external', +                           { 'id' => $esiid +                           } +                         ); +  return unless ($svc_obj); #debug +  $svcnum = $svc_obj->svcnum; + +  ### obtain _date +  $_date = time; + + +  ### got everything we needed +  #   now let insert it into usage_elec +  my %usage = ( +                'prev_date'        =>   $prev_date, +                'curr_date'        =>   $curr_date, +                'prev_read'        =>   $prev_read, +                'curr_read'        =>   $curr_read, +                'tdsp'             =>   $tdsp,        +                'svcnum'           =>   $svcnum, +                '_date'            =>   $_date, +                #'meter_multiplier' =>   $meter_multiplier, +                'meter_multiplier' =>   $meter_multiplier, +                'total_usage'      =>   $total_usage, +                'measured_demand'  =>   $measured_demand, +                'billed_demand'    =>   $billed_demand, +                'meter_number'     =>   $meter_number, +              ); +  print "usage_elect Dumping". Dumper(\%usage); +  +  if ( $edi_810->esiid != '10443720004466311' && +       $edi_810->esiid != '10443720004264904') { +    return; #for testing +  } +   +  my $usage_elec_obj = new FS::usage_elec( \%usage ); +  my $error = $usage_elec_obj->insert; +  print "I'm inserting something into usage_elec\n"; +  if ( $error ) { +    $dbh->rollback if $oldAutoCommit; +    my $msg = "Can't insert data into usage_elec : $error\n" +             .Dumper(\%usage); +    print "$msg"; +    return;  +  } +  +  $dbh->commit or die $dbh->errstr if $oldAutoCommit; + +  ### for testing purpose.. put a message into usage_elec_transaction_867 +  # +  my $usage_elec_transaction867_obj = new FS::usage_elec_transaction867(  +         {'usage_elec_id' => $usage_elec_obj->id, +          'note' => "Attention: a meter change out has occured at" +                   ." your location\n" +         }  +        ); + +  $error = $usage_elec_transaction867_obj->insert; +  print "Adding note into usage_elec_transaction867\n"; +  if ( $error ) { +    $dbh->rollback if $oldAutoCommit; +    my $msg = "Can't insert data into usage_elec_transaction867 : $error\n"; +    print "$msg"; +    return;  +  } +  +  $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + +  #exit; +  return $usage_elec_obj; + +} # end extract_data_to_usage_elec  + +### do the billing call +### return: if there is an error, returns the error, otherwise +###         returns false. +sub billing_call { +  my $usage_elec_obj = shift; + +  my $debug = 0; + +  my $svcnum = $usage_elec_obj->svcnum; +  print "svcnum = $svcnum\n" if $debug; + +  my $package = qsearchs (  'cust_svc',  +                           { 'svcnum' => $svcnum +                           } +                         ); +  unless ($package) { +    return "error: sub billing_call: unable to acquire the package\n"; +  } +  my $pkgnum = $package->pkgnum; +  print "pkgnum = $pkgnum\n" if $debug; + +  my $custpkg = qsearchs (  'cust_pkg', +                           { 'pkgnum'  => $pkgnum +                           } +                         );  +  unless ($custpkg) { +    return "error: sub billing_call: unable to acquire the custpkg\n"; +  } +  my $custnum = $custpkg->custnum; +  print "custnum = $custnum\n" if $debug; + +  my $cust_main_obj = qsearchs (  'cust_main', +                                 { 'custnum'  => $custnum +                                 } +                               );   +  unless ($cust_main_obj) { +    return "error: sub billing_call: unable to acquire the cust_main_obj\n"; +  } + +  my $rtnval = $cust_main_obj->bill(); +  if ($rtnval) { +    return "error: calling billing command\n\t$rtnval"; +  } + +  ### now let generate the invoice for the customer +  if ($debug) { #debug +    my $heading = "\tid\tprev_date\tcurr_date\tprev_read\tcurr_read" +                . "\ttdsp\tsvcnum\t_date\n"; + +    print "$heading"; +    print "\t" . $usage_elec_obj->id;  +    print "\t" . $usage_elec_obj->prev_date;  +    print "\t" . $usage_elec_obj->curr_date;  +    print "\t" . $usage_elec_obj->prev_read;  +    print "\t" . $usage_elec_obj->curr_read;  +    print "\t" . $usage_elec_obj->tdsp;  +    print "\t" . $usage_elec_obj->svcnum;  +    print "\t" . $usage_elec_obj->_date;  +    print "\t" . $usage_elec_obj->meter_multiplier; +    print "\t" . $usage_elec_obj->measured_demand; +    print "\t" . $usage_elec_obj->billed_demand; +    print "\n"; +  } + +  return; + +} #end billing_call + +1; + diff --git a/FS/FS/usage_elec_transaction867.pm b/FS/FS/usage_elec_transaction867.pm new file mode 100755 index 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'usage_elec_transaction867'; } + +=item insert + +Adds this record to the database.  If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database.  If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid example.  If there is +an error, returns the error, otherwise returns false.  Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { +  my $self = shift; + +  my $error =  +    $self->ut_numbern('id') +    || $self->ut_number('usage_elec_id') +    || $self->ut_textn('note') +  ; +  return $error if $error; + +  $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index 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";  | 
