fix discounting setup when recur < setup, RT#11512
[freeside.git] / FS / FS / usage_class.pm
1 package FS::usage_class;
2
3 use strict;
4 use vars qw( @ISA );
5 use FS::Record qw( qsearch qsearchs );
6 use FS::Conf;
7
8 my $conf = new FS::Conf;
9
10 @ISA = qw(FS::Record);
11
12 =head1 NAME
13
14 FS::usage_class - Object methods for usage_class records
15
16 =head1 SYNOPSIS
17
18   use FS::usage_class;
19
20   $record = new FS::usage_class \%hash;
21   $record = new FS::usage_class { 'column' => 'value' };
22
23   $error = $record->insert;
24
25   $error = $new_record->replace($old_record);
26
27   $error = $record->delete;
28
29   $error = $record->check;
30
31 =head1 DESCRIPTION
32
33 An FS::usage_class object represents a usage class.  Every rate detail
34 (see L<FS::rate_detail>) has, optionally, a usage class.  FS::usage_class
35 inherits from FS::Record.  The following fields are currently supported:
36
37 =over 4
38
39 =item classnum
40
41 Primary key (assigned automatically for new usage classes)
42
43 =item classname
44
45 Text name of this usage class
46
47 =item disabled
48
49 Disabled flag, empty or 'Y'
50
51
52 =back
53
54 =head1 METHODS
55
56 =over 4
57
58 =item new HASHREF
59
60 Creates a new usage class.  To add the usage class to the database,
61 see L<"insert">.
62
63 Note that this stores the hash reference, not a distinct copy of the hash it
64 points to.  You can ask the object for a copy with the I<hash> method.
65
66 =cut
67
68 sub table { 'usage_class'; }
69
70 =item insert
71
72 Adds this record to the database.  If there is an error, returns the error,
73 otherwise returns false.
74
75 =cut
76
77 =item delete
78
79 Delete this record from the database.
80
81 =cut
82
83 =item replace OLD_RECORD
84
85 Replaces the OLD_RECORD with this one in the database.  If there is an error,
86 returns the error, otherwise returns false.
87
88 =cut
89
90 =item check
91
92 Checks all fields to make sure this is a valid usage class.  If there is
93 an error, returns the error, otherwise returns false.  Called by the insert
94 and replace methods.
95
96 =cut
97
98 sub check {
99   my $self = shift;
100
101   my $error = 
102     $self->ut_numbern('classnum')
103     || $self->ut_numbern('weight')
104     || $self->ut_text('classname')
105     || $self->ut_textn('format')
106     || $self->ut_enum('disabled', [ '', 'Y' ])
107   ;
108   return $error if $error;
109
110   $self->SUPER::check;
111 }
112
113 =item summary_formats_labelhash
114
115 Returns a list of line item format descriptions suitable for assigning to
116 a hash. 
117
118 =cut
119
120 # transform hashes of arrays to arrays of hashes for false laziness removal?
121 my %summary_formats = (
122   'simple' => { 
123     'label' => [ qw( Description Calls Minutes Amount ) ],
124     'fields' => [
125                   sub { shift->{description} },
126                   sub { shift->{calls} },
127                   sub { sprintf( '%.1f', shift->{duration}/60 ) },
128                   sub { my($href, %opt) = @_; 
129                         ($opt{dollar} || ''). $href->{amount};
130                       },
131                 ],
132     'align'  => [ qw( l r r r ) ],
133     'span'   => [ qw( 4 1 1 1 ) ],            # unitprices?
134     'width'  => [ qw( 8.2cm 2.5cm 1.4cm 1.6cm ) ],   # don't like this
135     'show'   => 1,
136   },
137   'simpler' => { 
138     'label' =>  [ qw( Description Calls Amount ) ],
139     'fields' => [
140                   sub { shift->{description} },
141                   sub { shift->{calls} },
142                   sub { my($href, %opt) = @_; 
143                         ($opt{dollar} || ''). $href->{amount};
144                       },
145                 ],
146     'align'  => [ qw( l r r ) ],
147     'span'   => [ qw( 5 1 1 ) ],
148     'width'  => [ qw( 10.7cm 1.4cm 1.6cm ) ],   # don't like this
149     'show'   => 1,
150   },
151   'usage_simple' => { 
152     'label' => [ qw( Date Time Number Destination Duration Amount ) ],
153     'fields' => [
154                   sub { ' ' },
155                   sub { ' ' },
156                   sub { ' ' },
157                   sub { ' ' },
158                   sub { ' ' },
159                   sub { my $href = shift;  #ugh! making bunk of 'normalization'
160                         $href->{subtotal} ? $href->{subtotal} : ' '
161                       },
162                 ],
163     'align'  => [ qw( l l l l r r ) ],
164     'span'   => [ qw( 1 1 1 1 1 2 ) ],            # unitprices?
165     'width'  => [ qw( 4.3cm 1.4cm 2.5cm 2.5cm 1.4cm 1.6cm ) ],# don't like this
166     'show'   => 0,
167   },
168   'usage_6col' => { 
169     'label' => [ qw( col1 col2 col3 col4 col5 col6 ) ],
170     'fields' => [
171                   sub { ' ' },
172                   sub { ' ' },
173                   sub { ' ' },
174                   sub { ' ' },
175                   sub { ' ' },
176                   sub { my $href = shift;  #ugh! making bunk of 'normalization'
177                         $href->{subtotal} ? $href->{subtotal} : ' '
178                       },
179                 ],
180     'align'  => [ qw( l l l l r r ) ],
181     'span'   => [ qw( 1 1 1 1 1 2 ) ],            # unitprices?
182     'width'  => [ qw( 4.3cm 1.4cm 2.5cm 2.5cm 1.4cm 1.6cm ) ],# don't like this
183     'show'   => 0,
184    },
185   'usage_4col' => { 
186     'label' => [ qw( col1 col2 col3 col4 ) ],
187     'fields' => [
188                   sub { ' ' },
189                   sub { ' ' },
190                   sub { ' ' },
191                   sub { ' ' },
192                 ],
193     'align'  => [ qw( l l l l r r ) ],
194     'span'   => [ qw( 1 1 1 1 1 2 ) ],          
195     'width'  => [ qw( 4.3cm 1.4cm 2.5cm 2.5cm ) ],
196     'show'   => 0,
197   },
198   'usage_7col' => { 
199     'label' => [ qw( col1 col2 col3 col4 col5 col6 col7 ) ],
200     'fields' => [
201                   sub { ' ' },
202                   sub { ' ' },
203                   sub { ' ' },
204                   sub { ' ' },
205                   sub { ' ' },
206                   sub { ' ' },
207                   sub { my $href = shift;  #ugh! making bunk of 'normalization'
208                         $href->{subtotal} ? $href->{subtotal} : ' '
209                       },
210                 ],
211     'align'  => [ qw( l l l l l r r ) ],
212     'span'   => [ qw( 1 1 1 1 1 1 1 ) ],            # unitprices?
213     'width'  => [ qw( 2.9cm 1.4cm 1.4cm 2.5cm 2.5cm 1.4cm 1.6cm ) ],# don't like this
214     'show'   => 0,
215   },
216 );
217
218 sub summary_formats_labelhash {
219   map { $_ => join(',', @{$summary_formats{$_}{label}}) }
220     grep { $summary_formats{$_}{show} }
221     keys %summary_formats;
222 }
223
224 =item header_generator FORMAT
225
226 Returns a coderef used for generation of an invoice line item header for this
227 usage_class. FORMAT is either html or latex
228
229 =cut
230
231 my %html_align = (
232   'c' => 'center',
233   'l' => 'left',
234   'r' => 'right',
235 );
236
237 sub _generator_defaults {
238   my ( $self, $format, %opt ) = @_;
239   my %format = ( %{ $summary_formats{$self->format} }, %opt );
240   return ( \%format, ' ', ' ', ' ', sub { shift } );
241 }
242
243 sub header_generator {
244   my ( $self, $format, %opt ) = @_;
245
246   my ( $f, $prefix, $suffix, $separator, $column ) =
247     $self->_generator_defaults($format, %opt);
248
249   if ($format eq 'latex') {
250     $prefix = "\\hline\n\\rule{0pt}{2.5ex}\n\\makebox[1.4cm]{}&\n";
251     $suffix = "\\\\\n\\hline";
252     $separator = "&\n";
253     $column =
254       sub { my ($d,$a,$s,$w) = @_;
255             return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{\\textbf{$d}}}";
256           };
257   } elsif ( $format eq 'html' ) {
258     $prefix = '<th></th>';
259     $suffix = '';
260     $separator = '';
261     $column =
262       sub { my ($d,$a,$s,$w) = @_;
263             return qq!<th align="$html_align{$a}">$d</th>!;
264       };
265   }
266
267   sub {
268     my @args = @_;
269     my @result = ();
270
271     foreach  (my $i = 0; exists($f->{label}->[$i]); $i++) {
272       push @result,
273         &{$column}( map { $f->{$_}->[$i] } qw(label align span width) );
274     }
275
276     $prefix. join($separator, @result). $suffix;  
277   };
278
279 }
280
281 =item description_generator FORMAT
282
283 Returns a coderef used for generation of invoice line items for this
284 usage_class.  FORMAT is either html or latex
285
286 =cut
287
288 sub description_generator {
289   my ( $self, $format, %opt ) = @_;
290
291   my ( $f, $prefix, $suffix, $separator, $column ) =
292     $self->_generator_defaults($format, %opt);
293
294   my $money_char = '$';
295   if ($format eq 'latex') {
296     $prefix = "\\hline\n\\multicolumn{1}{c}{\\rule{0pt}{2.5ex}~} &\n";
297     $suffix = '\\\\';
298     $separator = " & \n";
299     $column =
300       sub { my ($d,$a,$s,$w) = @_;
301             return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{\\textbf{$d}}}";
302           };
303     $money_char = '\\dollar';
304   }elsif ( $format eq 'html' ) {
305     $prefix = '"><td align="center"></td>';
306     $suffix = '';
307     $separator = '';
308     $column =
309       sub { my ($d,$a,$s,$w) = @_;
310             return qq!<td align="$html_align{$a}">$d</td>!;
311       };
312     $money_char = $conf->config('money_char') || '$';
313   }
314
315   sub {
316     #my @args = @_;
317     my ($href) = shift;
318     my @result = ();
319
320     foreach  (my $i = 0; $f->{label}->[$i]; $i++) {
321       my $dollar = '';
322       $dollar = $money_char if $i == scalar(@{$f->{label}})-1;
323       push @result,
324         &{$column}( &{$f->{fields}->[$i]}($href, 'dollar' => $dollar),
325                     map { $f->{$_}->[$i] } qw(align span width)
326                   );
327     }
328
329     $prefix. join( $separator, @result ). $suffix;
330   };
331
332 }
333
334 =item total_generator FORMAT
335
336 Returns a coderef used for generation of invoice total lines for this
337 usage_class.  FORMAT is either html or latex
338
339 =cut
340
341 sub total_generator {
342   my ( $self, $format, %opt ) = @_;
343
344 #  $OUT .= '\FStotaldesc{' . $section->{'description'} . ' Total}' .
345 #          '{' . $section->{'subtotal'} . '}' . "\n";
346
347   my ( $f, $prefix, $suffix, $separator, $column ) =
348     $self->_generator_defaults($format, %opt);
349   my $style = '';
350
351   if ($format eq 'latex') {
352     $prefix = "& ";
353     $suffix = "\\\\\n";
354     $separator = " & \n";
355     $column =
356       sub { my ($d,$a,$s,$w) = @_;
357             return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{$d}}";
358           };
359   }elsif ( $format eq 'html' ) {
360     $prefix = '';
361     $suffix = '';
362     $separator = '';
363     $style = 'border-top: 3px solid #000000;border-bottom: 3px solid #000000;';
364     $column =
365       sub { my ($d,$a,$s,$w) = @_;
366             return qq!<td align="$html_align{$a}" style="$style">$d</td>!;
367       };
368   }
369   
370
371   sub {
372     my @args = @_;
373     my @result = ();
374
375     #  my $r = &{$f->{fields}->[$i]}(@args);
376     #  $r .= ' Total' unless $i;
377
378     foreach  (my $i = 0; $f->{label}->[$i]; $i++) {
379       push @result,
380         &{$column}( &{$f->{fields}->[$i]}(@args). ($i ? '' : ' Total'),
381                     map { $f->{$_}->[$i] } qw(align span width)
382                   );
383     }
384
385     $prefix. join( $separator, @result ). $suffix;
386   };
387
388 }
389
390 =item total_line_generator FORMAT
391
392 Returns a coderef used for generation of invoice total line items for this
393 usage_class.  FORMAT is either html or latex
394
395 =cut
396
397 # not used: will have issues with hash element names (description vs
398 # total_item and amount vs total_amount -- another array of functions?
399
400 sub total_line_generator {
401   my ( $self, $format, %opt ) = @_;
402
403 #     $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .
404 #             '{' . $line->{'total_amount'} . '}' . "\n";
405
406   my ( $f, $prefix, $suffix, $separator, $column ) =
407     $self->_generator_defaults($format, %opt);
408   my $style = '';
409
410   if ($format eq 'latex') {
411     $prefix = "& ";
412     $suffix = "\\\\\n";
413     $separator = " & \n";
414     $column =
415       sub { my ($d,$a,$s,$w) = @_;
416             return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{$d}}";
417           };
418   }elsif ( $format eq 'html' ) {
419     $prefix = '';
420     $suffix = '';
421     $separator = '';
422     $style = 'border-top: 3px solid #000000;border-bottom: 3px solid #000000;';
423     $column =
424       sub { my ($d,$a,$s,$w) = @_;
425             return qq!<td align="$html_align{$a}" style="$style">$d</td>!;
426       };
427   }
428   
429
430   sub {
431     my @args = @_;
432     my @result = ();
433
434     foreach  (my $i = 0; $f->{label}->[$i]; $i++) {
435       push @result,
436         &{$column}( &{$f->{fields}->[$i]}(@args),
437                     map { $f->{$_}->[$i] } qw(align span width)
438                   );
439     }
440
441     $prefix. join( $separator, @result ). $suffix;
442   };
443
444 }
445
446
447
448 sub _populate_initial_data {
449   my ($class, %opts) = @_;
450
451   foreach ("Intrastate", "Interstate", "International") {
452     my $object = $class->new( { 'classname' => $_ } );
453     my $error = $object->insert;
454     die "error inserting $class into database: $error\n"
455       if $error;
456   }
457
458   '';
459
460 }
461
462 sub _upgrade_data {
463   my $class = shift;
464
465   return $class->_populate_initial_data(@_)
466     unless scalar( qsearch( 'usage_class', {} ) );
467
468   '';
469
470 }
471
472 =back
473
474 =head1 BUGS
475
476 =head1 SEE ALSO
477
478 L<FS::Record>, schema.html from the base documentation.
479
480 =cut
481
482 1;
483