fix A/R report
[freeside.git] / bin / cdr-manual-rate
1 #!/usr/bin/perl
2
3 use FS::Record qw(qsearch qsearchs);
4 use FS::Misc::Getopt;
5 use FS::cdr;
6 use FS::Cursor;
7 our %opt;
8
9 getopts('f:');
10
11 unless ($opt{f}) {
12   die "
13   Usage:
14   cdr-manual-rate -f freesidestatus [ -s startdate ] [ -e enddate ] user
15 ";
16 }
17
18 $FS::UID::AutoCommit = 1; # because partial completion of this is useful
19
20 my $where_date;
21 if ($opt{start}) {
22   $where_date = " AND startdate >= $opt{start} ";
23 }
24 if ($opt{end}) {
25   $where_date .= " AND startdate < $opt{end} ";
26 }
27
28 my $cursor = FS::Cursor->new({
29     'table'     => 'cdr',
30     'hashref'   => { freesidestatus => $opt{f} },
31     'extra_sql' => $where_date,
32 });
33
34 our %svc_phone = (); # phonenum => svc_phone
35 our %pkgnum = ();    # phonenum => pkgnum
36 our %cust_pkg = ();  # pkgnum   => cust_pkg
37 our %pkgpart = ();   # phonenum => pkgpart
38 our %part_pkg = ();  # pkgpart  => part_pkg
39
40 # some stats
41 my $total = 0;
42 my $success = 0;
43 my $notfound = 0;
44 my $failed = 0;
45
46 while (my $cdr = $cursor->fetch) {
47
48     $total++;
49     my $cdrdesc = "CDR ". $cdr->acctid.", ".$cdr->src." -> ".$cdr->dst;
50
51     # borrow CDR-to-package matching code from cdrrated...
52     my $number = $cdr->charged_party || $cdr->src;
53
54     # strip the prefix from the number
55     my $prefix = '+1'; #$options{'default_prefix'};
56
57     $number = substr($number, length($prefix))
58       if $prefix eq substr($number, 0, length($prefix));
59     if ( $prefix && $prefix =~ /^\+(\d+)$/ ) {
60       $prefix = $1;
61       $number = substr($number, length($prefix))
62         if $prefix eq substr($number, 0, length($prefix));
63     }
64
65     # find a svc_phone that matches it
66     unless ( $svc_phone{$number} ) {
67       #only phone number matching supported right now
68       my $svc_phone = qsearchs('svc_phone', { 'phonenum' => $number } );
69       unless ( $svc_phone ) {
70         warn "can't find a phone service for $cdrdesc\n";
71         $notfound++;
72         next;
73       }
74
75       $svc_phone{$number} = $svc_phone;
76
77     }
78
79     # find the pkgnum
80     unless ( $pkgnum{$number} ) {
81
82       my $cust_pkg = $svc_phone{$number}->cust_svc->cust_pkg;
83       if (!$cust_pkg) {
84         warn "can't find a package for $cdrdesc\n";
85         $notfound++;
86         next;
87       }
88       $pkgnum{$number} = $cust_pkg->pkgnum;
89       $cust_pkg{$cust_pkg->pkgnum} ||= $cust_pkg;
90
91     }
92
93     unless ( $pkgpart{$number} ) {
94       #get the package, search through the part_pkg and linked for a voip_cdr def w/matching cdrtypenum (or no use_cdrtypenum)
95       my $cust_pkg = $cust_pkg{$pkgnum{$number}};
96       my @part_pkg;
97       foreach ($cust_pkg->part_pkg->self_and_bill_linked) {
98         if ($_->plan eq 'voip_cdr'
99                  && ( ! length($_->option_cacheable('use_cdrtypenum'))
100                       || $_->option_cacheable('use_cdrtypenum')
101                            eq $cdr->cdrtypenum #eq otherwise 0 matches ''
102                     )
103                  && ( ! length($_->option_cacheable('ignore_cdrtypenum'))
104                       || $_->option_cacheable('ignore_cdrtypenum')
105                            ne $cdr->cdrtypenum #ne otherwise 0 matches ''
106                     )
107
108         ) {
109           push @part_pkg, $_;
110         }
111       }
112
113       if (!@part_pkg) {
114         warn "can't find a voip_cdr package definition for $cdrdesc\n";
115         $notfound++;
116         next;
117       } elsif (scalar(@part_pkg) > 1) {
118         warn "found more than one package that could rate $cdrdesc\n";
119         $notfound++;
120         next;
121       }
122
123       $pkgpart{$number} = $part_pkg[0]->pkgpart;
124       $part_pkg{ $part_pkg[0]->pkgpart } ||= $part_pkg[0];
125
126     } # unless $pkgpart{$number}
127
128     # now actually rate the call. ignore included minutes, since that's a
129     # property of the billing cycle and this call isn't part of a billing
130     # cycle.
131     my $error = $cdr->rate(
132       'part_pkg'  => $part_pkg{ $pkgpart{$number} },
133       'cust_pkg'  => $cust_pkg{ $pkgnum{$number} },
134       'svcnum'    => $svc_phone{$number}->svcnum,
135     );
136     if ( $error ) {
137       warn "can't rate $cdrdesc: $error\n";
138       $failed++;
139       next;
140     }
141     $error = $cdr->set_status('done');
142     if ( $error ) {
143       # don't know how this would happen...
144       warn "can't set status on $cdrdesc: $error\n";
145       $failed++;
146       next;
147     }
148
149     $success++;
150 }
151
152 print "
153 Total CDRs:             $total
154 Incomplete information: $notfound
155 Failed rating:          $failed
156 Successfully rated:     $success
157 ";
158