more DTRT with usage on service transfer between packages and recharges RT #2884...
[freeside.git] / FS / FS / part_pkg / flat.pm
1 package FS::part_pkg::flat;
2
3 use strict;
4 use vars qw(@ISA %info);
5 use Tie::IxHash;
6 #use FS::Record qw(qsearch);
7 use FS::UI::bytecount;
8 use FS::part_pkg;
9
10 @ISA = qw(FS::part_pkg);
11
12 tie my %temporalities, 'Tie::IxHash',
13   'upcoming'  => "Upcoming (future)",
14   'preceding' => "Preceding (past)",
15 ;
16
17 %info = (
18   'name' => 'Flat rate (anniversary billing)',
19   'shortname' => 'Anniversary',
20   'fields' => {
21     'setup_fee'     => { 'name' => 'Setup fee for this package',
22                          'default' => 0,
23                        },
24     'recur_fee'     => { 'name' => 'Recurring fee for this package',
25                          'default' => 0,
26                        },
27
28     #false laziness w/voip_cdr.pm
29     'recur_temporality' => { 'name' => 'Charge recurring fee for period',
30                              'type' => 'select',
31                              'select_options' => \%temporalities,
32                            },
33
34     'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
35                                    ' of service at cancellation',
36                          'type' => 'checkbox',
37                        },
38     'externalid' => { 'name'   => 'Optional External ID',
39                       'default' => '',
40                     },
41     'seconds'       => { 'name' => 'Time limit for this package',
42                          'default' => '',
43                          'check' => sub { shift =~ /^\d*$/ },
44                        },
45     'upbytes'       => { 'name' => 'Upload limit for this package',
46                          'default' => '',
47                          'check' => sub { shift =~ /^\d*$/ },
48                          'format' => \&FS::UI::bytecount::display_bytecount,
49                          'parse' => \&FS::UI::bytecount::parse_bytecount,
50                        },
51     'downbytes'     => { 'name' => 'Download limit for this package',
52                          'default' => '',
53                          'check' => sub { shift =~ /^\d*$/ },
54                          'format' => \&FS::UI::bytecount::display_bytecount,
55                          'parse' => \&FS::UI::bytecount::parse_bytecount,
56                        },
57     'totalbytes'    => { 'name' => 'Transfer limit for this package',
58                          'default' => '',
59                          'check' => sub { shift =~ /^\d*$/ },
60                          'format' => \&FS::UI::bytecount::display_bytecount,
61                          'parse' => \&FS::UI::bytecount::parse_bytecount,
62                        },
63     'recharge_amount'       => { 'name' => 'Cost of recharge for this package',
64                          'default' => '',
65                          'check' => sub { shift =~ /^\d*(\.\d{2})?$/ },
66                        },
67     'recharge_seconds'      => { 'name' => 'Recharge time for this package',
68                          'default' => '',
69                          'check' => sub { shift =~ /^\d*$/ },
70                        },
71     'recharge_upbytes'      => { 'name' => 'Recharge upload for this package',
72                          'default' => '',
73                          'check' => sub { shift =~ /^\d*$/ },
74                          'format' => \&FS::UI::bytecount::display_bytecount,
75                          'parse' => \&FS::UI::bytecount::parse_bytecount,
76                        },
77     'recharge_downbytes'    => { 'name' => 'Recharge download for this package',
78                          'default' => '',
79                          'check' => sub { shift =~ /^\d*$/ },
80                          'format' => \&FS::UI::bytecount::display_bytecount,
81                          'parse' => \&FS::UI::bytecount::parse_bytecount,
82                        },
83     'recharge_totalbytes'   => { 'name' => 'Recharge transfer for this package',
84                          'default' => '',
85                          'check' => sub { shift =~ /^\d*$/ },
86                          'format' => \&FS::UI::bytecount::display_bytecount,
87                          'parse' => \&FS::UI::bytecount::parse_bytecount,
88                        },
89     'usage_rollover' => { 'name' => 'Allow usage from previous period to roll '.
90                                     ' over into current period',
91                           'type' => 'checkbox',
92                         },
93     'recharge_reset' => { 'name' => 'Reset usage to these values on manual '.
94                                     'package recharge',
95                           'type' => 'checkbox',
96                         },
97   },
98   'fieldorder' => [qw( setup_fee recur_fee recur_temporality unused_credit
99                        seconds upbytes downbytes totalbytes
100                        recharge_amount recharge_seconds recharge_upbytes
101                        recharge_downbytes recharge_totalbytes
102                        usage_rollover recharge_reset externalid
103                     )
104                   ],
105   'weight' => 10,
106 );
107
108 sub calc_setup {
109   my($self, $cust_pkg, $sdate, $details ) = @_;
110
111   my $i = 0;
112   my $count = $self->option( 'additional_count', 'quiet' ) || 0;
113   while ($i < $count) {
114     push @$details, $self->option( 'additional_info' . $i++ );
115   }
116
117   my $quantity = $cust_pkg->quantity || 1;
118
119   sprintf("%.2f", $quantity * $self->unit_setup($cust_pkg, $sdate, $details) );
120 }
121
122 sub unit_setup {
123   my($self, $cust_pkg, $sdate, $details ) = @_;
124
125   $self->option('setup_fee');
126 }
127
128 sub calc_recur {
129   my($self, $cust_pkg) = @_;
130
131   #my $last_bill = $cust_pkg->last_bill;
132   my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup
133
134   return 0
135     if $self->option('recur_temporality', 1) eq 'preceding' && $last_bill == 0;
136
137   $self->base_recur($cust_pkg);
138 }
139
140 sub base_recur {
141   my($self, $cust_pkg) = @_;
142   $self->option('recur_fee', 1) || 0;
143 }
144
145 sub base_recur_permonth {
146   my($self, $cust_pkg) = @_; #$cust_pkg?
147
148   return 0 unless $self->freq =~ /^\d+$/ && $self->freq > 0;
149
150   sprintf('%.2f', $self->base_recur / $self->freq );
151 }
152
153 sub calc_remain {
154   my ($self, $cust_pkg, %options) = @_;
155
156   my $time;
157   if ($options{'time'}) {
158     $time = $options{'time'};
159   } else {
160     $time = time;
161   }
162
163   my $next_bill = $cust_pkg->getfield('bill') || 0;
164
165   #my $last_bill = $cust_pkg->last_bill || 0;
166   my $last_bill = $cust_pkg->get('last_bill') || 0; #->last_bill falls back to setup
167
168   return 0 if    ! $self->base_recur
169               || ! $self->option('unused_credit', 1)
170               || ! $last_bill
171               || ! $next_bill
172               || $next_bill < $time;
173
174   my %sec = (
175     'h' =>    3600, # 60 * 60
176     'd' =>   86400, # 60 * 60 * 24
177     'w' =>  604800, # 60 * 60 * 24 * 7
178     'm' => 2629744, # 60 * 60 * 24 * 365.2422 / 12 
179   );
180
181   $self->freq =~ /^(\d+)([hdwm]?)$/
182     or die 'unparsable frequency: '. $self->freq;
183   my $freq_sec = $1 * $sec{$2||'m'};
184   return 0 unless $freq_sec;
185
186   sprintf("%.2f", $self->base_recur * ( $next_bill - $time ) / $freq_sec );
187
188 }
189
190 sub is_free_options {
191   qw( setup_fee recur_fee );
192 }
193
194 sub is_prepaid {
195   0; #no, we're postpaid
196 }
197
198 sub usage_valuehash {
199   my $self = shift;
200   map { $_, $self->option($_) } 
201     grep { $self->option($_, 'hush') } 
202     qw(seconds upbytes downbytes totalbytes);
203 }
204
205 sub reset_usage {
206   my($self, $cust_pkg, %opt) = @_;
207   warn "    resetting usage counters" if $opt{debug} > 1;
208   my %values = $self->usage_valuehash;
209   if ($self->option('usage_rollover', 1)) {
210     $cust_pkg->recharge(\%values);
211   }else{
212     $cust_pkg->set_usage(\%values, %opt);
213   }
214 }
215
216 1;