add svc_elec_features merged from reference code RT#7643
[freeside.git] / FS / FS / usage_elec.pm
1 package FS::usage_elec;
2
3 use strict;
4 use vars qw( @ISA @EXPORT_OK $me);
5 use FS::Record qw( qsearch qsearchs );
6 use FS::UID qw( getotaker dbh );
7 use FS::usage_elec_transaction867;
8 #use FS::cust_main;
9 use Exporter;
10 use List::Util qw[min max];
11 use Date::Format;
12 use HTTP::Date qw( str2time );
13 use Data::Dumper;
14 use Date::Calc qw(Delta_Days);
15 @ISA = qw(FS::Record Exporter);
16
17 @EXPORT_OK = qw( most_current_date curr_read edi_to_usage );
18
19 =head1 NAME
20
21 FS::usage_elec - Object methods for usage_elec records
22
23 =head1 SYNOPSIS
24
25   use FS::usage_elec;
26
27   $record = new FS::usage_elec \%hash;
28   $record = new FS::usage_elec { 'column' => 'value' };
29
30   $error = $record->insert;
31
32   $error = $new_record->replace($old_record);
33
34   $error = $record->delete;
35
36   $error = $record->check;
37
38 =head1 DESCRIPTION
39
40 An FS::usage_elec object represents an example.  FS::usage_elec inherits from
41 FS::Record.  The following fields are currently supported:
42
43 =over 4
44
45 =item id - primary key
46
47 =item prev_date - 
48
49 =item curr_date - 
50
51 =item prev_read - 
52
53 =item curr_read - 
54
55 =item tdsp - 
56
57 =item svcnum - 
58
59 =item _date - 
60
61
62 =back
63
64 =head1 METHODS
65
66 =over 4
67
68 =item new HASHREF
69
70 Creates a new example.  To add the example to the database, see L<"insert">.
71
72 Note that this stores the hash reference, not a distinct copy of the hash it
73 points to.  You can ask the object for a copy with the I<hash> method.
74
75 =cut
76
77 # the new method can be inherited from FS::Record, if a table method is defined
78
79 sub table { 'usage_elec'; }
80
81 =item insert
82
83 Adds this record to the database.  If there is an error, returns the error,
84 otherwise returns false.
85
86 =cut
87
88 # the insert method can be inherited from FS::Record
89
90 =item delete
91
92 Delete this record from the database.
93
94 =cut
95
96 # the delete method can be inherited from FS::Record
97
98 =item replace OLD_RECORD
99
100 Replaces the OLD_RECORD with this one in the database.  If there is an error,
101 returns the error, otherwise returns false.
102
103 =cut
104
105 # the replace method can be inherited from FS::Record
106
107 =item check
108
109 Checks all fields to make sure this is a valid example.  If there is
110 an error, returns the error, otherwise returns false.  Called by the insert
111 and replace methods.
112
113 =cut
114
115 # the check method should currently be supplied - FS::Record contains some
116 # data checking routines
117
118 sub check {
119   my $self = shift;
120
121   my $error = 
122     $self->ut_numbern('id')
123     || $self->ut_numbern('prev_date')
124     || $self->ut_numbern('curr_date')
125     || $self->ut_number('prev_read')
126     || $self->ut_number('curr_read')
127     || $self->ut_money('tdsp')
128     || $self->ut_number('svcnum')
129     || $self->ut_numbern('_date')
130     || $self->ut_float('meter_multiplier')
131     || $self->ut_numbern('demand_measure')
132     || $self->ut_numbern('demand_bill')
133   ;
134   return $error if $error;
135
136   $self->SUPER::check;
137 }
138
139 =back
140
141 =head1 BUGS
142
143 The author forgot to customize this manpage.
144
145 =head1 SEE ALSO
146
147 L<FS::Record>, schema.html from the base documentation.
148
149 =cut
150 sub most_current_date {
151  # my $self = shift;
152   my $cust_nr=shift;
153   my @custs = qsearch('usage_elec',{ 'cust_nr' => $cust_nr});
154
155   my $most_current_date  = 0;
156   
157   if (@custs) {
158     
159     foreach my $cust (@custs) {
160        if ($cust->curr_date > $most_current_date){
161                 $most_current_date = $cust;   
162         }
163     }
164   }
165   
166   return $most_current_date;
167
168 }
169
170 sub getUsage{
171         my $self = shift;
172         return $self->total_usage;
173 }
174 #sub getUsage{
175 #       my $self = shift;
176 #    my $prev_read=$self->prev_read;
177 #       my $curr_read=$self->curr_read;
178 #        my $usage;
179 #       if ($prev_read<=$curr_read) {
180 #               $usage= ($curr_read-$prev_read);
181 #       }
182 #       else{
183 #               $usage=(($curr_read+10**max(length($prev_read),length($curr_read)))-$prev_read);
184 #       }
185 #       return $usage*$self->meter_multiplier;
186 #}
187
188 sub getNumberOfDays {
189   my $self = shift;
190   return Date::Calc::Delta_Days( time2str('%Y', $self->prev_date), 
191                                  time2str('%L', $self->prev_date),
192                                  time2str('%e', $self->prev_date), 
193                                  time2str('%Y', $self->curr_date),
194                                  time2str('%L', $self->curr_date), 
195                                  time2str('%e', $self->curr_date)
196                                );
197 }
198
199
200 ### insert into table
201 #
202 sub insert_usage {
203   my $self = shift;
204  
205   my $debug = 0;
206   my $error;
207   
208   local $SIG{HUP} = 'IGNORE';
209   local $SIG{INT} = 'IGNORE';
210   local $SIG{QUIT} = 'IGNORE';
211   local $SIG{TERM} = 'IGNORE';
212   local $SIG{TSTP} = 'IGNORE';
213   local $SIG{PIPE} = 'IGNORE';
214  
215   my $oldAutoCommit = $FS::UID::AutoCommit;
216   local $FS::UID::AutoCommit = 0;
217   my $dbh = dbh;
218
219   $error = $self->check;
220   return $error if $error;
221
222   $error = $self->SUPER::insert;
223   if ( $error ) {
224     $dbh->rollback if $oldAutoCommit;
225     my $msg = "error: Can't insert data into usage_elec : $error\n"
226              .Dumper($self);
227     return $msg;
228   }
229  
230   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
231
232   return;
233 }
234
235 ### Take in a time and convert it to time string to be entered into usage_elec
236 ### the function use is str2time from module "HTTP::Date qw( str2time )"
237 sub to_usage_elec_time {
238   my ($time) = shift;
239
240   ### becareful using time2str, year allows are 1970-jan2038
241
242   return str2time($time);
243 }
244
245
246 # Get the past 10 usage for a particular svcnum and return the object
247 # return:
248 #  array of usages object
249 #  undef otherwise
250 #
251 #
252
253 sub query_usage {
254   my ($svcnum, $how_many) = @_;
255
256   #$how_many = 10 unless $how_many; # default to 10 usages
257
258 #  my @usages  = qsearch ( 
259 #                          'usage_elec', 
260 #                          {
261 #
262 #                           'svcnum' => $svcnum,
263 #                           # sort in DESCending order so it easier to splice
264 #                           # the array in the next step
265 #                           'extra_sql' => 'ORDER BY _date DESC'
266 #                          }
267 #                        );
268
269   my @usages = qsearch ( {
270                           'table'   => 'usage_elec',
271                           'hashref' => { 'svcnum' => $svcnum },
272                           'extra_sql' => 'ORDER BY _date DESC'
273                          } );
274
275   # shrink the array to $how_many index if it over the requested number
276   $#usages = $how_many - 1 if ( @usages && $how_many && (@usages > $how_many) );
277     
278
279   if (@usages) {
280     # since we query the usage by DESCending order, it a good idea to put it 
281     # in ascending order before a return.
282     @usages = reverse @usages;
283     return @usages;
284   }
285
286   return;
287 }
288
289
290 # sub routine that go through the transaction810 and transaction867 table
291 # to put data into usage_elec table
292 #
293 #
294 # some note
295 # to input data into usage_elect, all condition below must be meet
296 # 1. there is unprocess data from transaction810 table
297 # 2. there is unprocess data from transaction867 table
298 # 3. the unprocess data from transaction867 match transaction810
299 #    data.
300
301 sub edi_to_usage {
302   my $self = shift;
303
304   my $debug = 1;
305   #my @invoices_to_generate; # store usage_elec svcnum 
306
307   # Only send data to usage_elec if a transactin from 810 & 867 match up
308   #
309  
310   # first thing first.  Let get all edi from transaction_810 table that haven't
311   # been process
312   my @edi_810_processeds = qsearch ( 
313                             'transaction810', 
314                             {'processed' => '0'}
315                           );
316
317   unless (@edi_810_processeds) {
318     return "There were no un-process 810 to input into usage_elec.\n"
319           ."Run again when there is 810 data to process\n";
320   }
321
322   # second, let get all edi from transaction_867 table that haven't been 
323   # process
324   my @edi_867_processeds = qsearch ( 
325                             'transaction867', 
326                             {'processed' => '0'}
327                           );
328
329   unless (@edi_867_processeds) {
330     return "There were no un-process 867 to match up with 810 data.\n"
331           ."Run again when there is 867 data to process\n";
332   }
333
334   # third, match up the 810 and 867 data.  Those data that match up, goes
335   # into usage_elec table.
336
337   ### for efficientcy we will use the smaller list to traverse
338   if (@edi_810_processeds < @edi_867_processeds) {
339     
340     print "debug: using 810\n" if $debug;
341
342     foreach my $edi_810 (@edi_810_processeds) {
343       # find matching 867
344       my $ref_identification_810 = $edi_810->ref_identification;
345       my $srv_from_810 = $edi_810->srvc_from_date;
346       my $srv_to_810 = $edi_810->srvc_to_date;
347       ### search for the edi that match exactly with the 810
348       my $edi_867 = qsearchs ( 'transaction867',
349                                { 'ref_identification' => $ref_identification_810,
350                                  'srvc_from_date'     => $srv_from_810,
351                                  'srvc_to_date'       => $srv_to_810,
352                                }
353                             );
354       if ($edi_867) {
355         ### we have a match, extract the data and put into usage
356         my $usage_elec_obj = extract_data_to_usage_elec ($edi_810, $edi_867);
357         if ($usage_elec_obj) {
358
359           ### mark the 810 and 867 as already process
360           $edi_810->setfield('processed',1);
361           $edi_867->setfield('processed',1);
362
363           ### go ahead and billed 
364           my $rtnval = billing_call($usage_elec_obj);
365           if ($rtnval) {
366             print "Oh! Oh!.. unable to bill svcnum: $usage_elec_obj->svcnum\n";
367             print $rtnval;
368             $edi_810->setfield('processed',0);
369             $edi_867->setfield('processed',0);
370             $usage_elec_obj->delete;
371             return;
372           }
373
374         }
375         else {
376           print "RED ALERT.. something went wrong when inserting data\n"
377                ."into usage_elec (810)\n";
378           print "ref_identification of 810 : " . $edi_867->ref_identification
379                ."\n";
380           return;
381         }
382
383       }
384     }
385     
386   }
387   else {
388
389     print "debug: using 867\n" if $debug;
390
391     foreach my $edi_867 (@edi_867_processeds) {
392       # find matching 810
393       my $ref_identification_867 = $edi_867->ref_identification;
394       my $srv_from_867 = $edi_867->srvc_period_start_date;
395       my $srv_to_867 = $edi_867->srvc_period_end_date;
396       print "(debug) ref_identification: $ref_identification_867\n" if $debug;
397       ### search for the edi that match exactly with the 867
398       my $edi_810 = qsearchs ( 'transaction810',
399                                { 'ref_identification' => $ref_identification_867,
400                                  'srvc_from_date'     => $srv_from_867,
401                                  'srvc_to_date'       => $srv_to_867,
402                                }
403                              );
404       if ($edi_810) {
405
406         print "(debug) found an 810 that match the 867: esiid "
407              .$edi_810->esiid."\n" if $debug;
408
409         ### we have a match, extract the data and put into usage
410         my $usage_elec_obj = extract_data_to_usage_elec($edi_810, $edi_867);
411         if ($usage_elec_obj) {
412
413           ### mark the 810 and 867 as already process
414           my $edi_810_new = new FS::transaction810( { $edi_810->hash } );
415           $edi_810_new->setfield('processed',1);
416           my $error = $edi_810_new->replace($edi_810);
417           if ($error) {
418             print "there is an error changing column 'processed' of transaction810 table\n";
419             print "error: $error\n";
420           }
421
422           my $edi_867_new = new FS::transaction867( { $edi_867->hash } );
423           $edi_867->setfield('processed',1);
424           $error = $edi_867_new->replace($edi_867);
425           if ($error) {
426             print "there is an error changing column 'processed' of transaction867 table\n";
427             print "error: $error\n";
428           }
429
430           ### go ahead and billed 
431           my $rtnval = billing_call($usage_elec_obj);
432           if ($rtnval) {
433             print "Oh! Oh!.. unable to bill svcnum: $usage_elec_obj->svcnum\n";
434             print "$rtnval";
435             $edi_810->setfield('processed',0);
436             $edi_867->setfield('processed',0);
437             $usage_elec_obj->delete;
438             return;
439           }
440         }
441         else {
442           print "RED ALERT.. something went wrong when inserting data\n"
443                ."into usage_elec (810)\n";
444           print "ref_identification of 810 : " . $edi_810->ref_identification
445                ."\n";
446           #return;
447         }
448       }
449     }
450
451   }
452
453
454 }
455
456 # This subroutine does the physical adding of data into usage_elec
457 # using the transaction810 and transaction867 table
458
459 sub extract_data_to_usage_elec {
460   my ($edi_810, $edi_867) = @_;
461
462   ### variables declaration
463   ### following decl are column of usage_elec
464   my ($prev_date, $curr_date, $prev_read, $curr_read, $tdsp, $svcnum, $_date,
465       $meter_multiplier, $total_usage, $measured_demand, $billed_demand,
466       $meter_number);
467
468   local $SIG{HUP} = 'IGNORE';
469   local $SIG{INT} = 'IGNORE';
470   local $SIG{QUIT} = 'IGNORE';
471   local $SIG{TERM} = 'IGNORE';
472   local $SIG{TSTP} = 'IGNORE';
473   local $SIG{PIPE} = 'IGNORE';
474
475   my $oldAutoCommit = $FS::UID::AutoCommit;
476   local $FS::UID::AutoCommit = 0;
477   my $dbh = dbh;
478
479   ### this message will print in the year 2038 because their is a limitation
480   #   with str2time ( cpan.org package Icwa-1.0.0.tar.gz )
481   if ( int(time2str('%Y',time)) > 2037 ) {
482     print "Bug: Try to use function 'time2str' has generate an error because"
483          ."\n\tit can't handle year greter than 2037.\n";
484     return;
485   }   
486
487   # data from 867
488   $prev_date = str2time($edi_867->srvc_period_start_date);
489   $curr_date = str2time($edi_867->srvc_period_end_date);
490   $prev_read = $edi_867->prev_read_kwatts;
491   $curr_read = $edi_867->curr_read_kwatts;
492   $meter_multiplier = $edi_867->meter_multiplier;
493   $total_usage = $edi_867->usage_kwatts;
494   $measured_demand = $edi_867->measured_demand;
495   $meter_number = $edi_867->meter_no;
496
497   # data from 810
498   $tdsp = sprintf('%.2f',$edi_810->tdsp/100);
499   $billed_demand = $edi_810->billed_demand;
500
501  
502   ### obtain the svcnum
503   my $esiid = $edi_810->esiid;
504   my $svc_obj = qsearchs (  'svc_external',
505                            { 'id' => $esiid
506                            }
507                          );
508   return unless ($svc_obj); #debug
509   $svcnum = $svc_obj->svcnum;
510
511   ### obtain _date
512   $_date = time;
513
514
515   ### got everything we needed
516   #   now let insert it into usage_elec
517   my %usage = (
518                 'prev_date'        =>   $prev_date,
519                 'curr_date'        =>   $curr_date,
520                 'prev_read'        =>   $prev_read,
521                 'curr_read'        =>   $curr_read,
522                 'tdsp'             =>   $tdsp,       
523                 'svcnum'           =>   $svcnum,
524                 '_date'            =>   $_date,
525                 #'meter_multiplier' =>   $meter_multiplier,
526                 'meter_multiplier' =>   $meter_multiplier,
527                 'total_usage'      =>   $total_usage,
528                 'measured_demand'  =>   $measured_demand,
529                 'billed_demand'    =>   $billed_demand,
530                 'meter_number'     =>   $meter_number,
531               );
532   print "usage_elect Dumping". Dumper(\%usage);
533  
534   if ( $edi_810->esiid != '10443720004466311' &&
535        $edi_810->esiid != '10443720004264904') {
536     return; #for testing
537   }
538   
539   my $usage_elec_obj = new FS::usage_elec( \%usage );
540   my $error = $usage_elec_obj->insert;
541   print "I'm inserting something into usage_elec\n";
542   if ( $error ) {
543     $dbh->rollback if $oldAutoCommit;
544     my $msg = "Can't insert data into usage_elec : $error\n"
545              .Dumper(\%usage);
546     print "$msg";
547     return; 
548   }
549  
550   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
551
552   ### for testing purpose.. put a message into usage_elec_transaction_867
553   #
554   my $usage_elec_transaction867_obj = new FS::usage_elec_transaction867( 
555          {'usage_elec_id' => $usage_elec_obj->id,
556           'note' => "Attention: a meter change out has occured at"
557                    ." your location\n"
558          } 
559         );
560
561   $error = $usage_elec_transaction867_obj->insert;
562   print "Adding note into usage_elec_transaction867\n";
563   if ( $error ) {
564     $dbh->rollback if $oldAutoCommit;
565     my $msg = "Can't insert data into usage_elec_transaction867 : $error\n";
566     print "$msg";
567     return; 
568   }
569  
570   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
571
572
573   #exit;
574   return $usage_elec_obj;
575
576 } # end extract_data_to_usage_elec 
577
578 ### do the billing call
579 ### return: if there is an error, returns the error, otherwise
580 ###         returns false.
581 sub billing_call {
582   my $usage_elec_obj = shift;
583
584   my $debug = 0;
585
586   my $svcnum = $usage_elec_obj->svcnum;
587   print "svcnum = $svcnum\n" if $debug;
588
589   my $package = qsearchs (  'cust_svc', 
590                            { 'svcnum' => $svcnum
591                            }
592                          );
593   unless ($package) {
594     return "error: sub billing_call: unable to acquire the package\n";
595   }
596   my $pkgnum = $package->pkgnum;
597   print "pkgnum = $pkgnum\n" if $debug;
598
599   my $custpkg = qsearchs (  'cust_pkg',
600                            { 'pkgnum'  => $pkgnum
601                            }
602                          ); 
603   unless ($custpkg) {
604     return "error: sub billing_call: unable to acquire the custpkg\n";
605   }
606   my $custnum = $custpkg->custnum;
607   print "custnum = $custnum\n" if $debug;
608
609   my $cust_main_obj = qsearchs (  'cust_main',
610                                  { 'custnum'  => $custnum
611                                  }
612                                );  
613   unless ($cust_main_obj) {
614     return "error: sub billing_call: unable to acquire the cust_main_obj\n";
615   }
616
617   my $rtnval = $cust_main_obj->bill();
618   if ($rtnval) {
619     return "error: calling billing command\n\t$rtnval";
620   }
621
622   ### now let generate the invoice for the customer
623   if ($debug) { #debug
624     my $heading = "\tid\tprev_date\tcurr_date\tprev_read\tcurr_read"
625                 . "\ttdsp\tsvcnum\t_date\n";
626
627     print "$heading";
628     print "\t" . $usage_elec_obj->id; 
629     print "\t" . $usage_elec_obj->prev_date; 
630     print "\t" . $usage_elec_obj->curr_date; 
631     print "\t" . $usage_elec_obj->prev_read; 
632     print "\t" . $usage_elec_obj->curr_read; 
633     print "\t" . $usage_elec_obj->tdsp; 
634     print "\t" . $usage_elec_obj->svcnum; 
635     print "\t" . $usage_elec_obj->_date; 
636     print "\t" . $usage_elec_obj->meter_multiplier;
637     print "\t" . $usage_elec_obj->measured_demand;
638     print "\t" . $usage_elec_obj->billed_demand;
639     print "\n";
640   }
641
642   return;
643
644 } #end billing_call
645
646 1;
647