fix taxproduct fallout from IE 2083 limit workaround
[freeside.git] / httemplate / edit / part_pkg.cgi
1 <% include( 'elements/edit.html',
2               'post_url'              => popurl(1).'process/part_pkg.cgi',
3               'name'                  => "Package definition",
4               'table'                 => 'part_pkg',
5
6               'agent_virt'            => 1,
7               'agent_null_right'      => $edit_global,
8               'agent_clone_extra_sql' => FS::part_pkg->curuser_pkgs_sql,
9
10               #'viewall_dir'           => 'browse',
11               'viewall_url'           => $p.'browse/part_pkg.cgi',
12               'html_init'             => include('/elements/init_overlib.html').
13                                          $javascript,
14               'html_bottom'           => $html_bottom,
15               'body_etc'              =>
16                 'onLoad="agent_changed(document.edit_topform.agentnum)"',
17
18               'begin_callback'        => $begin_callback,
19               'end_callback'          => $end_callback,
20               'new_hashref_callback'  => $new_hashref_callback,
21               'new_object_callback'   => $new_object_callback,
22               'new_callback'          => $new_callback,
23               'clone_callback'        => $clone_callback,
24               'edit_callback'         => $edit_callback,
25               'error_callback'        => $error_callback,
26               'field_callback'        => $field_callback,
27
28               'labels' => { 
29                             'pkgpart'          => 'Package Definition',
30                             'pkg'              => 'Package (customer-visible)',
31                             'comment'          => 'Comment (customer-hidden)',
32                             'classnum'         => 'Package class',
33                             'promo_code'       => 'Promotional code',
34                             'freq'             => 'Recurring fee frequency',
35                             'setuptax'         => 'Setup fee tax exempt',
36                             'recurtax'         => 'Recurring fee tax exempt',
37                             'taxclass'         => 'Tax class',
38                             'taxproduct_select'=> 'Tax products',
39                             'plan'             => 'Price plan',
40                             'disabled'         => 'Disable new orders',
41                             'pay_weight'       => 'Payment weight',
42                             'credit_weight'    => 'Credit weight',
43                             'agentnum'         => 'Agent',
44                             'setup_fee'        => 'Setup fee',
45                             'recur_fee'        => 'Recurring fee',
46                             'bill_dst_pkgpart' => 'Include line item(s) from package',
47                             'svc_dst_pkgpart'  => 'Include services of package',
48                           },
49
50               'fields' => [
51                             { field=>'clone',  type=>'hidden',
52                               curr_value_callback =>
53                                 sub { shift->param('clone') },
54                             },
55                             { field=>'pkgnum', type=>'hidden',
56                               curr_value_callback =>
57                                 sub { shift->param('pkgnum') },
58                             },
59
60                             { type => 'columnstart' },
61                             
62                               { field     => 'pkg',
63                                 type      => 'text',
64                                 size      => 40, #32
65                                 maxlength => 50,
66                               },
67                               {field=>'comment',  type=>'text', size=>40 }, #32
68                               { field         => 'agentnum',
69                                 type          => 'select-agent',
70                                 disable_empty => ! $acl_edit_global,
71                                 empty_label   => '(global)',
72                                 onchange      => 'agent_changed',
73                               },
74                               {field=>'classnum', type=>'select-pkg_class' },
75                               {field=>'disabled', type=>$disabled_type, value=>'Y'},
76
77                               { type  => 'tablebreak-tr-title',
78                                 value => 'Pricing', #better name?
79                               },
80                               { field => 'plan',
81                                 type  => 'selectlayers-select',
82                                 options => [ keys %plan_labels ],
83                                 labels  => \%plan_labels,
84                               },
85                               { field => 'setup_fee',
86                                 type  => 'money',
87                               },
88                               { field    => 'freq',
89                                 type     => 'part_pkg_freq',
90                                 onchange => 'freq_changed',
91                               },
92                               { field    => 'recur_fee',
93                                 type     => 'money',
94                                 disabled => sub { $recur_disabled },
95                               },
96                                 
97                               #price plan
98                               #setup fee
99                               #recurring frequency
100                               #recurring fee (auto-disable)
101
102                             { type => 'columnnext' },
103
104                               {type=>'justtitle', value=>'Taxation' },
105                               {field=>'setuptax', type=>'checkbox', value=>'Y'},
106                               {field=>'recurtax', type=>'checkbox', value=>'Y'},
107                               {field=>'taxclass', type=>'select-taxclass' },
108                               { field => 'taxproductnums',
109                                 type  => 'hidden',
110                                 value => join(',', @taxproductnums),
111                               },
112                               { field => 'taxproduct_select',
113                                 type  => 'selectlayers',
114                                 options => [ '(default)', @taxproductnums ],
115                                 curr_value => '(default)',
116                                 labels  => { ( '(default)' => '(default)' ),
117                                              map {($_=>$usage_class{$_})}
118                                              @taxproductnums
119                                            },
120                                 layer_fields => \%taxproduct_fields,
121                                 layer_values_callback => $taxproduct_values,
122                                 layers_only  =>   !$taxproducts,
123                                 cell_style   => ( !$taxproducts
124                                                   ? 'display:none'
125                                                   : ''
126                                                 ),
127                               },
128
129                               { type  => 'tablebreak-tr-title',
130                                 value => 'Promotions', #better name?
131                               },
132                               { field=>'promo_code', type=>'text', size=>15 },
133
134                               { type  => 'tablebreak-tr-title',
135                                 value => 'Line-item revenue recogition', #better name?
136                               },
137                               { field=>'pay_weight',    type=>'text', size=>6 },
138                               { field=>'credit_weight', type=>'text', size=>6 },
139
140                             { type => 'columnnext' },
141
142                               { field    => 'agent_type',
143                                 type     => 'select-agent_types',
144                                 disabled => ! $acl_edit_global,
145                                 curr_value_callback => sub {
146                                   my($cgi, $object, $field) = @_;
147                                   #in the other callbacks..?  hmm.
148                                   \@agent_type;
149                                 },
150                               },
151
152                             { type => 'columnend' },
153
154                             { 'type'  => 'tablebreak-tr-title',
155                               'value' => 'Pricing add-ons',
156                             },
157                             { 'field'      => 'bill_dst_pkgpart',
158                               'type'       => 'select-part_pkg',
159                               'm2_label'   => 'Include line item(s) from package',
160                               'm2m_method' => 'bill_part_pkg_link',
161                               'm2m_dstcol' => 'dst_pkgpart',
162                               'm2_error_callback' =>
163                                 &{$m2_error_callback_maker}('bill'),
164                             },
165
166                             { type  => 'tablebreak-tr-title',
167                               value => 'Services',
168                             },
169                             { type => 'pkg_svc', },
170
171                             { 'field'      => 'svc_dst_pkgpart',
172                               'label'      => 'Also include services from package: ',
173                               'type'       => 'select-part_pkg',
174                               'm2_label'   => 'Include services of package: ',
175                               'm2m_method' => 'svc_part_pkg_link',
176                               'm2m_dstcol' => 'dst_pkgpart',
177                               'm2_error_callback' =>
178                                 &{$m2_error_callback_maker}('svc'),
179                             },
180
181                             { type  => 'tablebreak-tr-title',
182                               value => 'Price plan options',
183                             },
184
185                           ],
186
187            )
188 %>
189 <%init>
190
191 my $curuser = $FS::CurrentUser::CurrentUser;
192
193 my $edit_global = 'Edit global package definitions';
194 my $acl_edit        = $curuser->access_right('Edit package definitions');
195 my $acl_edit_global = $curuser->access_right($edit_global);
196
197 my $acl_edit_either = $acl_edit || $acl_edit_global;
198
199 my $begin_callback = sub {
200   my( $cgi, $fields, $opt ) = @_;
201   die "access denied"
202     unless $acl_edit_either
203         || ( $cgi->param('pkgnum')
204              && $curuser->access_right('Customize customer package')
205            );
206 };
207
208 my $disabled_type = $acl_edit_either ? 'checkbox' : 'hidden';
209
210 my $conf = new FS::Conf;
211 my $taxproducts = $conf->exists('enable_taxproducts');
212
213 #XXX
214 # - tr-part_pkg_freq: month_increments_only (from price plans)
215 # - test cloning
216 # - test errors cloning
217 # - test custom pricing
218 # - move the selectlayer divs away from lame layer_callback
219
220 #my ($query) = $cgi->keywords;
221 #
222 #my $part_pkg = '';
223
224 my @agent_type = ();
225 my %tax_override = ();
226
227 my %taxproductnums = map { ($_->classnum => 1) }
228                      qsearch('usage_class', { 'disabled' => '' });
229 my @taxproductnums = ( qw( setup recur ), sort (keys %taxproductnums) );
230
231 my %options = ();
232 my $recur_disabled = 1;
233
234 my $error_callback = sub {
235   my($cgi, $object, $fields, $opt ) = @_;
236
237   (@agent_type) = $cgi->param('agent_type');
238
239   $opt->{action} = 'Custom' if $cgi->param('pkgnum');
240
241   $recur_disabled = $cgi->param('freq') ? 0 : 1;
242
243   foreach ($cgi->param) {
244     /^usage_taxproductnum_(\d+)$/ && ($taxproductnums{$1} = 1);
245   }
246   $tax_override{''} = $cgi->param('tax_override');
247   $tax_override{$_} = $cgi->param('tax_override_$_')
248     foreach(grep { /^tax_override_(\w+)$/ } $cgi->param);
249
250   #some false laziness w/process
251   $cgi->param('plan') =~ /^(\w+)$/ or die 'unparsable plan';
252   my $plan = $1;
253   my $options = $cgi->param($plan."__OPTIONS");
254   my @options = split(',', $options);
255   %options =
256     map { my $optionname = $_;
257           my $param = $plan."__$optionname";
258           my $value = join(', ', $cgi->param($param));
259           ( $optionname => $value );
260         }
261         @options;
262
263   #$cgi->param($_, $options{$_}) foreach (qw( setup_fee recur_fee ));
264   $object->set($_ => scalar($cgi->param($_)) )
265     foreach (qw( setup_fee recur_fee ));
266
267 };
268
269 my $new_hashref_callback = sub { { 'plan' => 'flat' }; };
270
271 my $new_object_callback = sub {
272   my( $cgi, $hashref, $fields, $opt ) = @_;
273
274   my $part_pkg = FS::part_pkg->new( $hashref );
275   $part_pkg->set($_ => '0')
276     foreach (qw( setup_fee recur_fee ));
277
278   $part_pkg;
279
280 };
281
282 my $edit_callback = sub {
283   my( $cgi, $object, $fields, $opt ) = @_;
284
285   $recur_disabled = $object->freq ? 0 : 1;
286
287   (@agent_type) = map {$_->typenum} qsearch('type_pkgs',{'pkgpart'=>$1});
288
289   foreach ($object->options) {
290     /^usage_taxproductnum_(\d+)$/ && ($taxproductnums{$1} = 1);
291   }
292   foreach ($object->part_pkg_taxoverride) {
293     $taxproductnums{$_->usage_class} = 1
294       if $_->usage_class;
295   }
296
297   %options = $object->options;
298
299   $object->set($_ => $object->option($_))
300     foreach (qw( setup_fee recur_fee ));
301
302 };
303
304 my $new_callback = sub {
305   my( $cgi, $object, $fields ) = @_;
306
307   my $conf = new FS::Conf; 
308   if ( $conf->exists('agent_defaultpkg') ) {
309     #my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
310     @agent_type = map {$_->typenum} qsearch('agent_type',{});
311   }
312
313 };
314
315 my $clone_callback = sub {
316   my( $cgi, $object, $fields, $opt ) = @_;
317
318   if ( $cgi->param('pkgnum') ) {
319
320     my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cgi->param('pkgnum') } );
321     $object->agentnum( $cust_pkg->cust_main->agentnum );
322
323     $opt->{action} = 'Custom';
324
325     #my $part_pkg = $clone_part_pkg->clone;
326     #this is all clone did anyway
327     $object->comment( '(CUSTOM) '. $object->comment )
328       unless $object->comment =~ /^\(CUSTOM\) /;
329
330     $object->disabled('Y');
331
332   }
333
334   %options = $object->options;
335
336   $object->set($_ => $options{$_})
337     foreach (qw( setup_fee recur_fee ));
338
339   $recur_disabled = $object->freq ? 0 : 1;
340 };
341
342 my $m2_error_callback_maker = sub {
343   my $link_type = shift; #yay closures
344   return sub {
345     my( $cgi, $object ) = @_;
346       map  {
347              new FS::part_pkg_link {
348                'link_type'   => $link_type,
349                'src_pkgpart' => $object->pkgpart,
350                'dst_pkgpart' => $_,
351              };
352            }
353       grep $_,
354       map  $cgi->param($_),
355       grep /^${link_type}_dst_pkgpart(\d+)$/, $cgi->param;
356   };
357 };
358
359 my $javascript = <<'END';
360   <SCRIPT TYPE="text/javascript">
361
362     function freq_changed(what) {
363       var freq = what.options[what.selectedIndex].value;
364
365       if ( freq == '0' ) {
366         what.form.recur_fee.disabled = true;
367         what.form.recur_fee.style.backgroundColor = '#dddddd';
368       } else {
369         what.form.recur_fee.disabled = false;
370         what.form.recur_fee.style.backgroundColor = '#ffffff';
371       }
372
373     }
374
375     function agent_changed(what) {
376
377       var agentnum = what.options[what.selectedIndex].value;
378
379       if ( agentnum == 0 ) {
380         what.form.agent_type.disabled = false;
381         //what.form.agent_type.style.backgroundColor = '#ffffff';
382         what.form.agent_type.style.visibility = '';
383       } else {
384         what.form.agent_type.disabled = true;
385         //what.form.agent_type.style.backgroundColor = '#dddddd';
386         what.form.agent_type.style.visibility = 'hidden';
387       }
388
389     }
390
391   </SCRIPT>
392 END
393
394 tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
395
396 tie my %plan_labels, 'Tie::IxHash',
397   map {  $_ => ( $plans{$_}->{'shortname'} || $plans{$_}->{'name'} ) }
398       keys %plans;
399
400 my $html_bottom = sub {
401   my( $object ) = @_;
402
403   #warn join("\n", map { "$_: $options{$_}" } keys %options ). "\n";
404
405   my $layer_callback = sub {
406   
407     my $layer = shift;
408     my $html = ntable("#cccccc",2);
409   
410     #$html .= '
411     #  <TR>
412     #    <TD ALIGN="right">Recurring fee frequency </TD>
413     #    <TD><SELECT NAME="freq">
414     #';
415     #
416     #my @freq = keys %freq;
417     #@freq = grep { /^\d+$/ } @freq
418   #XXX this bit#  #  if exists($plans{$layer}->{'freq'}) && $plans{$layer}->{'freq'} eq 'm';
419     #foreach my $freq ( @freq ) {
420     #  $html .= qq(<OPTION VALUE="$freq");
421     #  $html .= ' SELECTED' if $freq eq $part_pkg->freq;
422     #  $html .= ">$freq{$freq}";
423     #}
424     #$html .= '</SELECT></TD></TR>';
425   
426     my $href = $plans{$layer}->{'fields'};
427     my @fields = exists($plans{$layer}->{'fieldorder'})
428                    ? @{$plans{$layer}->{'fieldorder'}}
429                    : keys %{ $href };
430   
431     foreach my $field ( grep $_ !~ /^(setup|recur)_fee$/, @fields ) {
432   
433       $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>';
434   
435       my $format = sub { shift };
436       $format = $href->{$field}{'format'} if exists($href->{$field}{'format'});
437
438       #XXX these should use elements/ fields... (or this whole thing should
439       #just use layer_fields instead of layer_callback)
440   
441       if ( ! exists($href->{$field}{'type'}) ) {
442   
443         $html .= qq!<INPUT TYPE="text" NAME="${layer}__$field" VALUE="!.
444                  ( exists($options{$field})
445                      ? &$format($options{$field})
446                      : $href->{$field}{'default'} ).
447                  qq!">!;
448   
449       } elsif ( $href->{$field}{'type'} eq 'checkbox' ) {
450   
451         $html .= qq!<INPUT TYPE="checkbox" NAME="${layer}__$field" VALUE=1 !.
452                  ( exists($options{$field}) && $options{$field}
453                    ? ' CHECKED'
454                    : ''
455                  ). '>';
456   
457       } elsif ( $href->{$field}{'type'} =~ /^select/ ) {
458   
459         $html .= '<SELECT';
460         $html .= ' MULTIPLE'
461           if $href->{$field}{'type'} eq 'select_multiple';
462         $html .= qq! NAME="${layer}__$field">!;
463   
464         if ( $href->{$field}{'select_table'} ) {
465           foreach my $record (
466             qsearch( $href->{$field}{'select_table'},
467                      $href->{$field}{'select_hash'}   )
468           ) {
469             my $value = $record->getfield($href->{$field}{'select_key'});
470             $html .= qq!<OPTION VALUE="$value"!.
471                      (  $options{$field} =~ /(^|, *)$value *(,|$)/ #?
472                           ? ' SELECTED'
473                           : ''
474                      ).
475                      '>'. $record->getfield($href->{$field}{'select_label'});
476           }
477         } elsif ( $href->{$field}{'select_options'} ) {
478           foreach my $key ( keys %{ $href->{$field}{'select_options'} } ) {
479             my $label = $href->{$field}{'select_options'}{$key};
480             $html .= qq!<OPTION VALUE="$key"!.
481                      ( $options{$field} =~ /(^|, *)$key *(,|$)/ #?
482                          ? ' SELECTED'
483                          : ''
484                      ).
485                      '>'. $label;
486           }
487   
488         } else {
489           $html .= '<font color="#ff0000">warning: '.
490                    "don't know how to retreive options for $field select field".
491                    '</font>';
492         }
493         $html .= '</SELECT>';
494   
495       } elsif ( $href->{$field}{'type'} eq 'radio' ) {
496   
497         my $radio =
498           qq!<INPUT TYPE="radio" NAME="${layer}__$field"!;
499   
500         foreach my $key ( keys %{ $href->{$field}{'options'} } ) {
501           my $label = $href->{$field}{'options'}{$key};
502           $html .= qq!$radio VALUE="$key"!.
503                    ( $options{$field} =~ /(^|, *)$key *(,|$)/ #?
504                        ? ' CHECKED'
505                        : ''
506                    ).
507                    "> $label<BR>";
508         }
509   
510       }
511   
512       $html .= '</TD></TR>';
513     }
514     $html .= '</TABLE>';
515   
516     $html .= qq(<INPUT TYPE="hidden" NAME="${layer}__OPTIONS" VALUE=").
517              join(',', keys %{ $href } ). '">';
518   
519     $html;
520   
521   };
522
523   my %selectlayers = (
524     field          => 'plan',
525     options        => [ keys %plan_labels ],
526     labels         => \%plan_labels,
527     curr_value     => $object->plan,
528     layer_callback => $layer_callback,
529   );
530
531   my $return =
532     include('/elements/selectlayers.html', %selectlayers, 'layers_only'=>1 ).
533     '<SCRIPT TYPE="text/javascript">'.
534       include('/elements/selectlayers.html', %selectlayers, 'js_only'=>1 );
535
536   $return .=
537     "taxproduct_selectchanged(document.getElementById('taxproduct_select'));\n"
538       if $taxproducts;
539
540   $return .= '</SCRIPT>';
541
542   $return;
543
544 };
545
546 my %usage_class = map { ($_->classnum => $_->classname) }
547                   qsearch('usage_class', {});
548 $usage_class{setup} = 'Setup';
549 $usage_class{recur} = 'Recurring';
550
551 my %taxproduct_fields = ();
552 my $end_callback = sub {
553   my( $cgi, $object, $fields, $opt ) = @_;
554
555   @taxproductnums = ( qw( setup recur ), sort (keys %taxproductnums) );
556
557   if ( $object->pkgpart ) {
558     foreach my $usage_class ( '', @taxproductnums ) {
559       $tax_override{$usage_class} =
560         join (",", map $_->taxclassnum,
561                        qsearch( 'part_pkg_taxoverride', {
562                                   'pkgpart'     => $object->pkgpart,
563                                   'usage_class' => $usage_class,
564                               })
565              );
566     }
567   }
568
569   %taxproduct_fields =
570     map { $_ => [ "taxproductnum_$_", 
571                   { type  => 'select-taxproduct',
572                     #label => "$usage_class{$_} tax product",
573                   },
574                   "tax_override_$_", 
575                   { type  => 'select-taxoverride' }
576                 ]
577         }
578         @taxproductnums;
579
580   $taxproduct_fields{'(default)'} =
581     [ 'taxproductnum', { type => 'select-taxproduct',
582                          #label => 'Default tax product',
583                        },
584       'tax_override',  { type => 'select-taxoverride' },
585     ];
586 };
587
588 my $taxproduct_values = sub {
589   my ($cgi, $object, $flags) = @_;
590   my $routine =
591     sub { my $layer = shift;
592           my @fields = @{$taxproduct_fields{$layer}};
593           my @values = ();
594           while( @fields ) {
595             my $field = shift @fields;
596             shift @fields;
597             $field =~ /^taxproductnum_\w+$/ &&
598               push @values, ( $field => $options{"usage_$field"} );
599             $field =~ /^tax_override_(\w+)$/ &&
600               push @values, ( $field => $tax_override{$1} );
601             $field =~ /^taxproductnum$/ &&
602               push @values, ( $field => $object->taxproductnum );
603             $field =~ /^tax_override$/ &&
604               push @values, ( $field => $tax_override{''} );
605           }
606           { (@values) };
607         };
608   
609   my @result = 
610     map { ( $_ => { &{$routine}($_) } ) } ( '(default)', @taxproductnums );
611   return({ @result });
612   
613 };
614
615 my $field_callback = sub {
616   my ($cgi, $object, $fieldref) = @_;
617
618   my $field = $fieldref->{field};
619   if ($field eq 'taxproductnums') {
620     $fieldref->{value} = join(',', @taxproductnums);
621   } elsif ($field eq 'taxproduct_select') {
622     $fieldref->{options} = [ '(default)', @taxproductnums ];
623     $fieldref->{labels}  = { ( '(default)' => '(default)' ),
624                              map {( $_ => ($usage_class{$_} || $_) )}
625                                @taxproductnums
626                            };
627     $fieldref->{layer_fields} = \%taxproduct_fields;
628     $fieldref->{layer_values_callback} = $taxproduct_values;
629   }
630 };
631
632 </%init>