Option to ignore old CDRs, RT#81480
[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' => $agent_clone_extra_sql,
9               #'viewall_dir'           => 'browse',
10               'viewall_url'           => $p.'browse/part_pkg.cgi',
11               'html_init'             => include('/elements/init_overlib.html').
12                                          include('/elements/init_calendar.html').
13                                          $javascript,
14               'html_bottom'           => $html_bottom,
15               'body_etc'              =>
16                 'onLoad="agent_changed(document.edit_topform.agentnum);
17                          hide_supp_pkgs()"',
18
19               'begin_callback'        => $begin_callback,
20               'end_callback'          => $end_callback,
21               'new_hashref_callback'  => $new_hashref_callback,
22               'new_object_callback'   => $new_object_callback,
23               'new_callback'          => $new_callback,
24               'clone_callback'        => $clone_callback,
25               'edit_callback'         => $edit_callback,
26               'error_callback'        => $error_callback,
27               'field_callback'        => $field_callback,
28
29               'onsubmit'              => 'confirm_submit',
30
31               'labels' => { 
32                             'pkgpart'          => 'Package Definition',
33                             'pkg'              => 'Package',
34                             %locale_field_labels,
35                             'comment'          => 'Comment (customer-hidden)',
36                             'classnum'         => 'Package class',
37                             'addon_classnum'   => 'Restrict additional orders to package class',
38                             'promo_code'       => 'Promotional code',
39                             'freq'             => 'Recurring fee frequency',
40                             'setuptax'         => 'Setup fee tax exempt',
41                             'recurtax'         => 'Recurring fee tax exempt',
42                             'taxclass'         => 'Tax class',
43                             'taxproduct_select'=> 'Tax products',
44                             'plan'             => 'Price plan',
45                             'disabled'         => 'Disable new orders',
46                             'disable_line_item_date_ranges' => 'Disable line item date ranges',
47                             'start_on_hold'    => 'Start on hold',
48                             'setup_cost'       => 'Setup cost',
49                             'recur_cost'       => 'Recur cost',
50                             'pay_weight'       => 'Payment weight',
51                             'credit_weight'    => 'Credit weight',
52                             'agent_pkgpartid'  => 'External ID',
53                             'agentnum'         => 'Agent',
54                             'agent_type'       => ' ', #just its title headingn is fine
55                             'setup_fee'        => 'Setup fee',
56                             'setup_show_zero'  => 'Show zero setup',
57                             'recur_fee'        => 'Recurring fee',
58                             'recur_show_zero'  => 'Show zero recurring',
59                             'discountnum'      => 'Offer discounts for longer terms',
60                             'bill_dst_pkgpart' => 'Include line item(s) from package',
61                             'svc_dst_pkgpart'  => 'Include services of package',
62                             'supp_dst_pkgpart' => 'When ordering package, also order',
63                             'report_option'    => 'Report classes',
64                             'fcc_ds0s'         => 'Voice-grade equivalents',
65                             'fcc_voip_class'   => 'Category',
66                             'delay_start'      => 'Default delay (days)',
67                           },
68
69               'fields' => [
70                             { field=>'clone',  type=>'hidden',
71                               curr_value_callback =>
72                                 sub { shift->param('clone') },
73                             },
74                             { field=>'pkgnum', type=>'hidden',
75                               curr_value_callback =>
76                                 sub { shift->param('pkgnum') },
77                             },
78
79                             { field=>'custom',  type=>'hidden' },
80                             { field=>'family_pkgpart', type=>'hidden' },
81                             { field=>'successor', type=>'hidden' },
82
83                             { type => 'columnstart' },
84                             
85                               { field     => 'pkg',
86                                 type      => 'input-locale-text',
87                                 size      => 40, #32
88                                 maxlength => 50,
89                               },
90                               #@locale_fields,
91                               {field=>'comment',  type=>'text', size=>40 }, #32
92                               { field         => 'agentnum',
93                                 type          => 'select-agent',
94                                 disable_empty => ! $acl_edit_global,
95                                 empty_label   => '(global)',
96                                 onchange      => 'agent_changed',
97                               },
98                               {field=>'classnum', type=>'select-pkg_class' },
99                               ( $conf->exists('pkg-addon_classnum')
100                                   ? ( { field=>'addon_classnum',
101                                         type =>'select-pkg_class',
102                                       }
103                                     )
104                                    : ()
105                               ),
106                               {field=>'disabled', type=>$disabled_type, value=>'Y'},
107                               {field=>'disable_line_item_date_ranges', type=>$disabled_type, value=>'Y'},
108                               { field => 'start_on_hold',
109                                 type => 'checkbox',
110                                 value => 'Y'
111                               },
112
113                               { type     => 'tablebreak-tr-title',
114                                 value    => 'Pricing', #better name?
115                               },
116                               { field    => 'plan',
117                                 type     => 'selectlayers-select',
118                                 options  => [ keys %plan_labels ],
119                                 labels   => \%plan_labels,
120                                 onchange => 'aux_planchanged(what);',
121                               },
122                               { field    => 'setup_fee',
123                                 type     => 'money',
124                                 onchange => 'setup_changed',
125                               },
126                               { field    => 'setup_show_zero',
127                                 type     => 'checkbox',
128                                 value    => 'Y',
129                                 disabled => sub { $setup_show_zero_disabled },
130                               },
131                               { field    => 'freq',
132                                 type     => 'part_pkg_freq',
133                                 onchange => 'freq_changed',
134                               },
135                               { field    => 'recur_fee',
136                                 type     => 'money',
137                                 disabled => sub { $recur_disabled },
138                                 onchange => 'recur_changed',
139                               },
140
141                               { field    => 'recur_show_zero',
142                                 type     => 'checkbox',
143                                 value    => 'Y',
144                                 disabled => sub { $recur_show_zero_disabled },
145                               },
146
147                               #price plan
148                               #setup fee
149                               #recurring frequency
150                               #recurring fee (auto-disable)
151
152                             { type => 'columnnext' },
153
154                               {type=>'justtitle', value=>'Taxation' },
155                               {field=>'setuptax', type=>'checkbox', value=>'Y'},
156                               {field=>'recurtax', type=>'checkbox', value=>'Y'},
157                               {field=>'taxclass', type=>'select-taxclass' },
158                               { field => 'taxproductnums',
159                                 type  => 'hidden',
160                                 value => join(',', @taxproductnums),
161                               },
162                               { field => 'taxproduct_select',
163                                 type  => 'selectlayers',
164                                 options => [ '(default)', @taxproductnums ],
165                                 curr_value => '(default)',
166                                 labels  => { ( '(default)' => '(default)' ),
167                                              map {($_=>$usage_class{$_})}
168                                              @taxproductnums
169                                            },
170                                 layer_fields => \%taxproduct_fields,
171                                 layer_values_callback => $taxproduct_values,
172                                 layers_only  =>   !$taxproducts,
173                                 cell_style   => ( !$taxproducts
174                                                   ? 'display:none'
175                                                   : ''
176                                                 ),
177                               },
178
179                               { type  => 'tablebreak-tr-title',
180                                 value => 'Promotions', #better name?
181                               },
182                               { field=>'promo_code', type=>'text', size=>15 },
183
184                               { type  => 'tablebreak-tr-title',
185                                 value => 'Cost tracking', #better name?
186                               },
187
188                               ( $curuser->access_right('Edit package definition costs')
189                                 ? ( { field=>'setup_cost', type=>'money', },
190                                     { field=>'recur_cost', type=>'money', },
191                                   )
192                                 : ( { field=>'setup_cost', type=>'fixed', },
193                                     { field=>'recur_cost', type=>'fixed', },
194                                   )
195                               ),
196
197                               ( $conf->exists('part_pkg-delay_start')
198                                 ? ( { type  => 'tablebreak-tr-title',
199                                       value => 'Delayed start',
200                                     },
201                                     { field => 'delay_start',
202                                       type => 'text', size => 6 },
203                                   )
204                                 : ()
205                               ),
206
207                             { type => 'columnnext' },
208
209                               {type=>'justtitle', value=>'Agent (reseller) types' },
210                               
211                               { field       => 'agent_type',
212                                 type        => 'select-agent_type',
213                                 disabled    => ! $acl_edit_global,
214                                 element_etc => 'size="10"',
215                                 multiple    =>  '1', #cause edit.html is dum
216                                 curr_value_callback => sub {
217                                   my($cgi, $object, $field) = @_;
218                                   #in the other callbacks..?  hmm.
219                                   \@agent_type;
220                                 },
221                               },
222
223                       { type  => 'tablebreak-tr-title',
224                         value => 'External Links', #better name?
225                       },
226                       { field=>'agent_pkgpartid', type=>'text', size=>21 },
227
228                       { type  => 'tablebreak-tr-title',
229                         value => 'Line-item revenue recogition', #better name?
230                       },
231                       { field=>'pay_weight',    type=>'text', size=>6 },
232                       { field=>'credit_weight', type=>'text', size=>6 },
233                     ($fcc_opts ? ( 
234                       { type  => 'tablebreak-tr-title',
235                         value => 'FCC Form 477 information',
236                       },
237                       { field => 'fcc_options_string',
238                         type  => 'input-fcc_options',
239                         curr_value_callback => sub {
240                           my ($cgi, $part_pkg, $fref) = @_;
241                           if ( $cgi->param('fcc_options_string') ) {
242                             # error redirect
243                             return $cgi->param('fcc_options_string');
244                           }
245                           my %hash;
246                           %hash = $part_pkg->fcc_options 
247                             if ($part_pkg->pkgpart);
248                           return encode_json(\%hash);
249                         },
250                       },
251                       ) : ()
252                     ),
253                             { type => 'columnend' },
254
255                             { 'type'  => $report_option ? 'tablebreak-tr-title'
256                                                         : 'hidden',
257                               'value' => 'Optional report classes',
258                               'field' => 'census_title',
259                             },
260                             { 'field'    => 'report_option',
261                               'type'     => $report_option ? 'select-table'
262                                                            : 'hidden',
263                               'table'    => 'part_pkg_report_option',
264                               'name_col' => 'name',
265                               'hashref'  => { 'disabled' => '' },
266                               'multiple' => 1,
267                             },
268
269                             { 'type'    => 'tablebreak-tr-title',
270                               'value'   => 'Term discounts',
271                             },
272                             { 'field'      => 'discountnum',
273                               'type'       => 'select-table',
274                               'table'      => 'discount',
275                               'name_col'   => 'name',
276                               'hashref'    => { %$discountnum_hashref },
277                               #'extra_sql'  => 'AND (months IS NOT NULL OR months != 0)',
278                               'empty_label'=> 'Select discount',
279                               'm2_label'   => 'Offer discounts for longer terms',
280                               'm2m_method' => 'part_pkg_discount',
281                               'm2m_dstcol' => 'discountnum',
282                               'm2_error_callback' => $discount_error_callback,
283                             },
284
285                             { 'type'    => 'tablebreak-tr-title',
286                               'value'   => 'Pricing add-ons',
287                               'colspan' => 4,
288                             },
289                             { 'field'      => 'bill_dst_pkgpart',
290                               'type'       => 'select-part_pkg',
291                               'extra_sql'  => sub { $pkgpart
292                                                      ? "AND pkgpart != $pkgpart"
293                                                      : ''
294                                                   },
295 +                             'label_callback' => sub { shift->pkg_comment_only },
296                               'm2_label'   => 'Include line item(s) from package',
297                               'm2m_method' => 'bill_part_pkg_link',
298                               'm2m_dstcol' => 'dst_pkgpart',
299                               'm2_error_callback' =>
300                                 &{$m2_error_callback_maker}('bill'),
301                               'm2_fields' => [ { 'field' => 'hidden',
302                                                  'type'  => 'checkbox',
303                                                  'value' => 'Y',
304                                                  'curr_value' => '',
305                                                  'label' => 'Bundle',
306                                                },
307                                              ],
308                             },
309
310                             { type  => 'tablebreak-tr-title',
311                               value => 'Services',
312                             },
313                             { type => 'pkg_svc', },
314
315                             { 'field'      => 'svc_dst_pkgpart',
316                               'label'      => 'Also include services from package: ',
317                               'type'       => 'select-part_pkg',
318                               'extra_sql'  => sub { $pkgpart
319                                                      ? "AND pkgpart != $pkgpart"
320                                                      : ''
321                                                   },
322 +                             'label_callback' => sub { shift->pkg_comment_only },
323                               'm2_label'   => 'Include services of package: ',
324                               'm2m_method' => 'svc_part_pkg_link',
325                               'm2m_dstcol' => 'dst_pkgpart',
326                               'm2_error_callback' =>
327                                 &{$m2_error_callback_maker}('svc'),
328                             },
329
330                             { 'type'    => 'tablebreak-tr-title',
331                               'value'   => 'Supplemental packages',
332                               'colspan' => '4',
333                               'include_opt_callback' => sub {
334                                  'id' => 'show_supp_pkgs',
335                               },
336                             },
337                             { 'field'       => 'supp_dst_pkgpart',
338                               'type'        => 'select-part_pkg',
339 +                             'label_callback' => sub { shift->pkg_comment_only },
340                               'm2_label'    => 'When ordering package, also order',
341                               'm2m_method'  => 'supp_part_pkg_link',
342                               'm2m_dstcol'  => 'dst_pkgpart',
343                               'm2_error_callback' =>
344                                 &{$m2_error_callback_maker}('supp'),
345                             },
346
347                             { type  => 'tablebreak-tr-title',
348                               value => 'Price plan options',
349                             },
350
351                           ],
352
353            )
354 %>
355 <%init>
356
357 my $curuser = $FS::CurrentUser::CurrentUser;
358
359 my $edit_global = 'Edit global package definitions';
360 my $acl_edit        = $curuser->access_right('Edit package definitions');
361 my $acl_edit_global = $curuser->access_right($edit_global);
362
363 my $acl_edit_either = $acl_edit || $acl_edit_global;
364
365 my $begin_callback = sub {
366   my( $cgi, $fields, $opt ) = @_;
367   die "access denied"
368     unless $acl_edit_either
369         || ( $cgi->param('pkgnum')
370              && $curuser->access_right('Customize customer package')
371            );
372 };
373
374 my $disabled_type = $acl_edit_either ? 'checkbox' : 'hidden';
375
376 #arg.  access rights for cloning are Hard.
377 # on the one hand we don't really want cloning (customizing a package) to fail 
378 #  for want of finding the source package in normal usage
379 # on the other hand, we don't want people using the clone link to be able to
380 #  see 
381 my $agent_clone_extra_sql = 
382   ' ( '. FS::part_pkg->curuser_pkgs_sql.
383   "   OR ( part_pkg.custom = 'Y' ) ".
384   ' ) ';
385
386 my $conf = new FS::Conf;
387 my $taxproducts = $conf->exists('enable_taxproducts');
388
389 my $fcc_opts = $conf->exists('part_pkg-show_fcc_options');
390
391 my @locales = grep { ! /^en_/i } $conf->config('available-locales'); #should filter from the default locale lang instead of en_
392 my %locale_labels =  map {
393   ( $_ => 'Package -- '. FS::Locales->description($_) )
394 } @locales;
395 @locales = 
396   sort { $locale_labels{$a} cmp $locale_labels{$b} }
397     @locales;
398
399 my $n = 0;
400 my %locale_field_labels = (
401   map {
402         ( 'pkgpartmsgnum'. $n++. '_pkg' => $locale_labels{$_} );
403       }
404     @locales
405 );
406
407 my $sth = dbh->prepare("SELECT COUNT(*) FROM part_pkg_report_option".
408                        "  WHERE disabled IS NULL OR disabled = ''  ")
409   or die dbh->errstr;
410 $sth->execute or die $sth->errstr;
411 my $report_option = $sth->fetchrow_arrayref->[0];
412
413 #XXX
414 # - tr-part_pkg_freq: month_increments_only (from price plans)
415 # - test cloning
416 # - test errors cloning
417 # - test custom pricing
418 # - move the selectlayer divs away from lame layer_callback
419
420 #my ($query) = $cgi->keywords;
421 #
422 #my $part_pkg = '';
423
424 my @agent_type = ();
425 my %tax_override = ();
426
427 my %taxproductnums = map { ($_->classnum => 1) }
428                      qsearch('usage_class', { 'disabled' => '' });
429 my @taxproductnums = ( qw( setup recur ), sort (keys %taxproductnums) );
430
431 my %options = ();
432 my $recur_disabled = 1;
433 my $setup_show_zero_disabled = 0;
434 my $recur_show_zero_disabled = 1;
435
436 my $pkgpart = '';
437
438 my $error_callback = sub {
439   my($cgi, $object, $fields, $opt ) = @_;
440
441   (@agent_type) = $cgi->param('agent_type');
442
443   $opt->{action} = 'Custom' if $cgi->param('pkgnum');
444
445   $setup_show_zero_disabled = ($cgi->param('setup_fee') > 0) ? 1 : 0;
446
447   $recur_disabled = $cgi->param('freq') ? 0 : 1;
448   $recur_show_zero_disabled =
449     $cgi->param('freq')
450       ? $cgi->param('recur_fee') > 0 ? 1 : 0
451       : 1;
452
453   foreach ($cgi->param) {
454     /^usage_taxproductnum_(\d+)$/ && ($taxproductnums{$1} = 1);
455   }
456   $tax_override{''} = $cgi->param('tax_override');
457   $tax_override{$_} = $cgi->param('tax_override_$_')
458     foreach(grep { /^tax_override_(\w+)$/ } $cgi->param);
459
460   #some false laziness w/process
461   $cgi->param('plan') =~ /^(\w+)$/ or die 'unparsable plan';
462   my $plan = $1;
463   my $options = $cgi->param($plan."__OPTIONS");
464   my @options = split(',', $options);
465   %options =
466     map { my $optionname = $_;
467           my $param = $plan."__$optionname";
468           my $value = join(', ', $cgi->param($param));
469           ( $optionname => $value );
470         }
471         @options;
472
473   $object->set($_ => scalar($cgi->param($_)) )
474     foreach (qw( setup_fee recur_fee disable_line_item_date_ranges ));
475
476   $pkgpart = $object->pkgpart;
477
478 };
479
480 my $new_hashref_callback = sub { { 'plan' => 'flat' }; };
481
482 my $new_object_callback = sub {
483   my( $cgi, $hashref, $fields, $opt ) = @_;
484
485   my $part_pkg = FS::part_pkg->new( $hashref );
486   $part_pkg->set($_ => '0')
487     foreach (qw( setup_fee recur_fee disable_line_item_date_ranges ));
488
489   $part_pkg;
490
491 };
492
493 sub set_report_option {
494   my($cgi, $object, $fields ) = @_; #, $opt
495
496   my @report_option = ();
497   foreach ($object->options) {
498     /^usage_taxproductnum_(\d+)$/ && ($taxproductnums{$1} = 1);
499     /^report_option_(\d+)$/ && (push @report_option, $1);
500   }
501   foreach ($object->part_pkg_taxoverride) {
502     $taxproductnums{$_->usage_class} = 1
503       if $_->usage_class;
504   }
505
506   $cgi->param('report_option', join(',', @report_option));
507   foreach my $field ( @$fields ) {
508     next unless ( 
509       ref($field) eq 'HASH' &&
510       $field->{field} &&
511       $field->{field} eq 'report_option'
512     );
513     #$field->{curr_value} = join(',', @report_option);
514     $field->{value} = join(',', @report_option);
515   }
516
517 }
518
519 my $edit_callback = sub {
520   my( $cgi, $object, $fields, $opt ) = @_;
521
522   $setup_show_zero_disabled = ($object->option('setup_fee') > 0) ? 1 : 0;
523
524   $recur_disabled = $object->freq ? 0 : 1;
525
526   $recur_show_zero_disabled =
527     $object->freq
528       ? $object->option('recur_fee') > 0 ? 1 : 0
529       : 1;
530
531   (@agent_type) =
532     map {$_->typenum} qsearch('type_pkgs', { 'pkgpart' => $object->pkgpart } );
533
534   set_report_option( $cgi, $object, $fields);
535
536   %options = $object->options;
537
538   $object->set($_ => $object->option($_, 1))
539     foreach (qw( setup_fee recur_fee disable_line_item_date_ranges ));
540
541   $pkgpart = $object->pkgpart;
542
543 };
544
545 my $new_callback = sub {
546   my( $cgi, $object, $fields ) = @_;
547
548   my $conf = new FS::Conf; 
549
550   if ( $conf->exists('agent_defaultpkg') ) {
551     @agent_type = map {$_->typenum} qsearch('agent_type', { 'disabled'=>'' });
552   }
553
554   $options{'suspend_bill'}=1 if $conf->exists('part_pkg-default_suspend_bill');
555
556 };
557
558 my $clone_callback = sub {
559   my( $cgi, $object, $fields, $opt ) = @_;
560
561   if ( $cgi->param('pkgnum') ) {
562
563     my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => scalar($cgi->param('pkgnum')) } );
564     $object->agentnum( $cust_pkg->cust_main->agentnum );
565
566     $opt->{action} = 'Custom';
567
568     #my $part_pkg = $clone_part_pkg->clone;
569     #this is all clone does anyway
570     $object->custom('Y');
571
572     $object->disabled('Y');
573
574   } else { #when explicitly cloning, not customizing
575
576     (@agent_type) =
577       map {$_->typenum} qsearch('type_pkgs',{ 'pkgpart' => $object->pkgpart } );
578
579   }
580
581   set_report_option( $cgi, $object, $fields);
582
583   %options = $object->options;
584
585   $object->set($_ => $options{$_})
586     foreach (qw( setup_fee recur_fee disable_line_item_date_ranges ));
587
588   $recur_disabled = $object->freq ? 0 : 1;
589
590   $recur_show_zero_disabled =
591     $object->freq
592       ? $object->option('recur_fee') > 0 ? 1 : 0
593       : 1;
594
595 };
596
597 my $discount_error_callback = sub {
598   my( $cgi, $object ) = @_;
599   map {
600         if ( /^discountnum(\d+)$/ &&
601              ( my $discountnum = $cgi->param("discountnum$1") ) )
602         {
603           new FS::part_pkg_discount {
604             'pkgpart'     => $object->pkgpart,
605             'discountnum' => $discountnum,
606           };
607         } else {
608           ();
609         }
610       }
611   $cgi->param;
612 };
613
614 my $m2_error_callback_maker = sub {
615   my $link_type = shift; #yay closures
616   return sub {
617     my( $cgi, $object ) = @_;
618     map {
619
620           if ( /^${link_type}_dst_pkgpart(\d+)$/ &&
621                ( my $dst = $cgi->param("${link_type}_dst_pkgpart$1") ) )
622           {
623
624             my $hidden = $cgi->param("${link_type}_dst_pkgpart__hidden$1")
625                          || '';
626             new FS::part_pkg_link {
627               'link_type'   => $link_type,
628               'src_pkgpart' => $object->pkgpart,
629               'dst_pkgpart' => $dst,
630               'hidden'      => $hidden,
631             };
632           } else {
633             ();
634           }
635         }
636     $cgi->param;
637   };
638 };
639
640 my $javascript = <<'END';
641   <SCRIPT TYPE="text/javascript">
642
643     function freq_changed(what) {
644       var freq = what.options[what.selectedIndex].value;
645
646       if ( freq == '0' ) {
647         what.form.recur_fee.disabled = true;
648         what.form.recur_fee.style.backgroundColor = '#dddddd';
649         what.form.recur_show_zero.disabled = true;
650         //what.form.recur_show_zero.style.backgroundColor= '#dddddd';
651       } else {
652         what.form.recur_fee.disabled = false;
653         what.form.recur_fee.style.backgroundColor = '#ffffff';
654         recur_changed( what.form.recur_fee );
655         //what.form.recur_show_zero.style.backgroundColor= '#ffffff';
656       }
657
658     }
659
660     function setup_changed(what) {
661       var setup = what.value;
662       if ( parseFloat(setup) == 0 ) {
663         what.form.setup_show_zero.disabled = false;
664       } else {
665         what.form.setup_show_zero.disabled = true;
666       }
667     }
668
669     function recur_changed(what) {
670       var recur = what.value;
671       if ( parseFloat(recur) == 0 ) {
672         what.form.recur_show_zero.disabled = false;
673       } else {
674         what.form.recur_show_zero.disabled = true;
675       }
676     }
677
678     function agent_changed(what) {
679
680       var agentnum;
681       if ( what.type == 'select-one' ) {
682         agentnum = what.options[what.selectedIndex].value;
683       } else {
684         agentnum = what.value;
685       }
686
687       if ( agentnum == 0 ) {
688         what.form.agent_type.disabled = false;
689         //what.form.agent_type.style.backgroundColor = '#ffffff';
690         what.form.agent_type.style.visibility = '';
691       } else {
692         what.form.agent_type.disabled = true;
693         //what.form.agent_type.style.backgroundColor = '#dddddd';
694         what.form.agent_type.style.visibility = 'hidden';
695       }
696
697     }
698
699     function aux_planchanged(what) { //?
700
701       var plan = what.options[what.selectedIndex].value;
702       var table = document.getElementById('TableNumber6') // XXX NOT ROBUST
703
704       if ( plan == 'flat' || plan == 'prorate' || plan == 'subscription' ) {
705         //table.disabled = false;
706         table.style.visibility = '';
707       } else {
708         //table.disabled = true;
709         table.style.visibility = 'hidden';
710       }
711
712     }
713
714     // some magic to make "supplemental packages" less obvious
715     var supp_pkg_rows = [];
716     function show_supp_pkgs_click() {
717       supp_pkg_rows[0].style.display = '';
718       this.onclick = '';
719       this.style.backgroundColor = '';
720       this.style.border = '';
721       this.style.padding = '';
722     }
723
724     function hide_supp_pkgs() {
725       var all_selects = document.getElementsByTagName('select');
726       for (var i=0; i < all_selects.length; i++) {
727         if ( all_selects[i].id.match(/^supp_dst_pkgpart/) ) {
728           supp_pkg_rows.push( all_selects[i].parentNode.parentNode );
729         }
730       }
731       if ( supp_pkg_rows.length == 1 ) {
732         // there are none configured, so hide the row to create a new one
733         supp_pkg_rows[0].style.display = 'none';
734         var button = document.getElementById('show_supp_pkgs');
735         button.onclick = show_supp_pkgs_click;
736         button.style.backgroundColor = '#cccccc';
737         button.style.border = '1px solid #7e0079';
738         button.style.padding = '1px';
739       }
740     }
741
742     function finish_edit_fcc(id) {
743       cClick();
744       show_fcc_options(id); // refresh the display
745     }
746
747 END
748
749 my $warning =
750   'Changing the setup or recurring fee will create a new package definition. '.
751   'Continue?';
752
753 $javascript .= "function confirm_submit(f) {";
754 if ( $conf->exists('part_pkg-lineage') ) {
755   $javascript .= "
756
757     var fields = Array('setup_fee','recur_fee');
758     for(var i=0; i < fields.length; i++) {
759         if ( f[fields[i]].value != f[fields[i]].defaultValue ) {
760             return confirm('$warning');
761         }
762     }
763 ";
764 }
765 $javascript .= "
766   return true;
767 }
768 </SCRIPT>";
769
770 tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
771
772 tie my %plan_labels, 'Tie::IxHash',
773   map {  $_ => ( $plans{$_}->{'shortname'} || $plans{$_}->{'name'} ) }
774       keys %plans;
775
776 my $html_bottom = sub {
777   my( $object ) = @_;
778
779   #warn join("\n", map { "$_: $options{$_}" } keys %options ). "\n";
780
781   my $layer_callback = sub {
782   
783     my $layer = shift;
784     my $html = ntable("#cccccc",2);
785   
786     #$html .= '
787     #  <TR>
788     #    <TD ALIGN="right">Recurring fee frequency </TD>
789     #    <TD><SELECT NAME="freq">
790     #';
791     #
792     #my @freq = keys %freq;
793     #@freq = grep { /^\d+$/ } @freq
794   #XXX this bit#  #  if exists($plans{$layer}->{'freq'}) && $plans{$layer}->{'freq'} eq 'm';
795     #foreach my $freq ( @freq ) {
796     #  $html .= qq(<OPTION VALUE="$freq");
797     #  $html .= ' SELECTED' if $freq eq $part_pkg->freq;
798     #  $html .= ">$freq{$freq}";
799     #}
800
801    #$html .= '</SELECT></TD></TR>';
802   
803     my $href = $plans{$layer}->{'fields'};
804     my @fields;
805     if ( $plans{$layer}->{'fieldorder'} ) {
806       @fields = @{ $plans{$layer}->{'fieldorder'} };
807     } else {
808       warn "FS::part_pkg::$layer has no fieldorder.\n";
809       @fields = keys %$href;
810     }
811     
812     # hash of dependencies for each of the Pricing Plan fields.
813     # make sure NOT to use double-quotes inside the 'msg' value.
814     my $dependencies = {
815         'unused_credit_suspend' => {
816             'msg'       => q|You must set the 'suspend_credit_type' option in Configuration->Settings to gain access to this option.|,
817             'are_met'   => sub{
818                 my $conf = new FS::conf;
819                 my @conf_info = qsearch('conf', { 'name' => 'suspend_credit_type' } );
820                 return 1 if (exists($conf_info[0]) && $conf_info[0]->{Hash}{value});
821                 return 0;
822             }
823         },
824         'unused_credit_cancel' => {
825             'msg'       => q|You must set the 'cancel_credit_type' option in Configuration->Settings to gain access to this option.|,
826             'are_met'   => sub{
827                 my $conf = new FS::conf;
828                 my @conf_info = qsearch('conf', { 'name' => 'cancel_credit_type' } );
829                 return 1 if (exists($conf_info[0]) && $conf_info[0]->{Hash}{value});
830                 return 0;
831             }
832         }
833     };
834
835     foreach my $field ( grep $_ !~ /^(setup|recur)_fee$/, @fields ) {
836   
837       if(!exists($href->{$field})) {
838         # shouldn't happen
839         warn "nonexistent part_pkg option: '$field'\n";
840         next;
841       }
842       if ( exists($href->{$field}->{display_if}) ) {
843         my %args = ( 'plan' => $layer ); # anything else?
844         my $display = &{ $href->{$field}->{display_if} }(%args);
845         next if !$display;
846       }
847
848       $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>
849       ';
850   
851       my $format = sub { shift };
852       $format = $href->{$field}{'format'} if exists($href->{$field}{'format'});
853
854       #XXX these should use elements/ fields... (or this whole thing should
855       #just use layer_fields instead of layer_callback)
856   
857       if (exists($dependencies->{$field}) && !$dependencies->{$field}{'are_met'}()) {
858           $html .= q!<span title="!.$dependencies->{$field}{'msg'}.q!">N/A</span>!;
859           
860       } elsif ( ! exists($href->{$field}{'type'}) ) {
861   
862         $html .= qq!<INPUT TYPE="text" NAME="${layer}__$field" VALUE="!.
863                  ( exists($options{$field})
864                      ? &$format($options{$field})
865                      : $href->{$field}{'default'} ).
866                  qq!">!;
867   
868       } elsif ( $href->{$field}{'type'} eq 'textarea' ) {
869
870         $html .= qq!<TEXTAREA NAME="${layer}__$field">!.
871                  ( exists($options{$field})
872                      ? &$format($options{$field})
873                      : $href->{$field}{'default'} ).
874                  qq!</TEXTAREA>!;
875
876       } elsif ( $href->{$field}{'type'} eq 'checkbox' ) {
877   
878         $html .= qq!<INPUT TYPE="checkbox" NAME="${layer}__$field" VALUE=1 !.
879                  ( exists($options{$field}) && $options{$field}
880                    ? ' CHECKED'
881                    : ''
882                  ). '>';
883
884       } elsif ( $href->{$field}{'type'} eq 'date' ) {
885
886         $html .= include('/elements/input-date-field.html', {
887                            'name'  => $layer.'__'.$field,
888                            'value' => $options{$field},
889                         });
890
891       } elsif ( $href->{$field}{'type'} =~ /^select-rt-/ ) {
892
893         $html .= include('/elements/'.$href->{$field}{'type'}.'.html',
894                            'name'       => $layer.'__'.$field,
895                            'curr_value' => $options{$field},
896                            map { $_ => $href->{$field}{$_} }
897                              grep { $_ !~ /^(name|type|parse)$/ }
898                                keys %{ $href->{$field} }
899                         );
900
901       } elsif ( $href->{$field}{'type'} eq 'select-rate' ) {
902
903         $html .= include('/elements/select-rate.html',
904                            'field'      => $layer.'__'.$field,
905                            'curr_value' => $options{$field},
906                            map { $_ => $href->{$field}{$_} }
907                              grep { $_ !~ /^(name|type)$/ }
908                                keys %{ $href->{$field} }
909                         );
910
911       } elsif ( $href->{$field}{'type'} =~ /^select/ ) {
912   
913         $html .= '<SELECT';
914         $html .= ' MULTIPLE'
915           if $href->{$field}{'type'} eq 'select_multiple';
916         $html .= qq! NAME="${layer}__$field">!;
917
918         $html .= '<OPTION VALUE="">'. $href->{$field}{'empty_label'}
919           if exists($href->{$field}{'disable_empty'})
920                && ! $href->{$field}{'disable_empty'};
921   
922         if ( $href->{$field}{'select_table'} ) {
923           foreach my $record (
924             qsearch( $href->{$field}{'select_table'},
925                      $href->{$field}{'select_hash'}   )
926           ) {
927             my $value = $record->getfield($href->{$field}{'select_key'});
928             $html .= qq!<OPTION VALUE="$value"!.
929                      (  $options{$field} =~ /(^|, *)$value *(,|$)/ #?
930                           ? ' SELECTED'
931                           : ''
932                      ).
933                      '>'. $record->getfield($href->{$field}{'select_label'});
934           }
935         } elsif ( $href->{$field}{'select_options'} ) {
936           foreach my $key ( keys %{ $href->{$field}{'select_options'} } ) {
937             my $label = $href->{$field}{'select_options'}{$key};
938             $html .= qq!<OPTION VALUE="$key"!.
939                      ( $options{$field} =~ /(^|, *)$key *(,|$)/ #?
940                          ? ' SELECTED'
941                          : ''
942                      ).
943                      '>'. $label;
944           }
945   
946         } else {
947           $html .= '<font color="#ff0000">warning: '.
948                    "don't know how to retreive options for $field select field".
949                    '</font>';
950         }
951         $html .= '</SELECT>';
952   
953       } elsif ( $href->{$field}{'type'} eq 'radio' ) {
954   
955         my $radio =
956           qq!<INPUT TYPE="radio" NAME="${layer}__$field"!;
957   
958         foreach my $key ( keys %{ $href->{$field}{'options'} } ) {
959           my $label = $href->{$field}{'options'}{$key};
960           $html .= qq!$radio VALUE="$key"!.
961                    ( $options{$field} =~ /(^|, *)$key *(,|$)/ #?
962                        ? ' CHECKED'
963                        : ''
964                    ).
965                    "> $label<BR>";
966         }
967   
968       }
969   
970       $html .= '</TD></TR>';
971     }
972     $html .= '</TABLE>';
973  
974     $html .= include('/elements/hidden.html',
975                 field => $layer.'__OPTIONS',
976                 value => join(',', @fields)
977              );
978   
979     $html;
980   
981   };
982
983   my %selectlayers = (
984     field          => 'plan',
985     options        => [ keys %plan_labels ],
986     labels         => \%plan_labels,
987     curr_value     => $object->plan,
988     layer_callback => $layer_callback,
989     onchange       => 'aux_planchanged(what);',
990   );
991
992   my $return =
993     include('/elements/selectlayers.html', %selectlayers, 'layers_only'=>1 ).
994     '<SCRIPT TYPE="text/javascript">'.
995       include('/elements/selectlayers.html', %selectlayers, 'js_only'=>1 );
996
997   $return .=
998     "taxproduct_selectchanged(document.getElementById('taxproduct_select'));\n"
999       if $taxproducts;
1000
1001   $return .= '</SCRIPT>';
1002
1003   $return;
1004
1005 };
1006
1007 my %usage_class = map { ($_->classnum => $_->classname) }
1008                   qsearch('usage_class', {});
1009 $usage_class{setup} = 'Setup';
1010 $usage_class{recur} = 'Recurring';
1011
1012 my %taxproduct_fields = ();
1013 my $end_callback = sub {
1014   my( $cgi, $object, $fields, $opt ) = @_;
1015
1016   @taxproductnums = ( qw( setup recur ), sort (keys %taxproductnums) );
1017
1018   if ( $object->pkgpart ) {
1019     foreach my $usage_class ( '', @taxproductnums ) {
1020       $tax_override{$usage_class} =
1021         join (",", map $_->taxclassnum,
1022                        qsearch( 'part_pkg_taxoverride', {
1023                                   'pkgpart'     => $object->pkgpart,
1024                                   'usage_class' => $usage_class,
1025                               })
1026              );
1027     }
1028   }
1029
1030   %taxproduct_fields =
1031     map { $_ => [ "taxproductnum_$_", 
1032                   { type  => 'select-taxproduct',
1033                     #label => "$usage_class{$_} tax product",
1034                   },
1035                   "tax_override_$_", 
1036                   { type  => 'select-taxoverride' }
1037                 ]
1038         }
1039         @taxproductnums;
1040
1041   $taxproduct_fields{'(default)'} =
1042     [ 'taxproductnum', { type => 'select-taxproduct',
1043                          #label => 'Default tax product',
1044                        },
1045       'tax_override',  { type => 'select-taxoverride' },
1046     ];
1047 };
1048
1049 my $taxproduct_values = sub {
1050   my ($cgi, $object, $flags) = @_;
1051   my $routine =
1052     sub { my $layer = shift;
1053           my @fields = @{$taxproduct_fields{$layer}};
1054           my @values = ();
1055           while( @fields ) {
1056             my $field = shift @fields;
1057             shift @fields;
1058             $field =~ /^taxproductnum_\w+$/ &&
1059               push @values, ( $field => $options{"usage_$field"} );
1060             $field =~ /^tax_override_(\w+)$/ &&
1061               push @values, ( $field => $tax_override{$1} );
1062             $field =~ /^taxproductnum$/ &&
1063               push @values, ( $field => $object->taxproductnum );
1064             $field =~ /^tax_override$/ &&
1065               push @values, ( $field => $tax_override{''} );
1066           }
1067           { (@values) };
1068         };
1069   
1070   my @result = 
1071     map { ( $_ => { &{$routine}($_) } ) } ( '(default)', @taxproductnums );
1072   return({ @result });
1073   
1074 };
1075
1076 my $field_callback = sub {
1077   my ($cgi, $object, $fieldref) = @_;
1078
1079   my $field = $fieldref->{field};
1080   if ($field eq 'taxproductnums') {
1081     $fieldref->{value} = join(',', @taxproductnums);
1082   } elsif ($field eq 'taxproduct_select') {
1083     $fieldref->{options} = [ '(default)', @taxproductnums ];
1084     $fieldref->{labels}  = { ( '(default)' => '(default)' ),
1085                              map {( $_ => ($usage_class{$_} || $_) )}
1086                                @taxproductnums
1087                            };
1088     $fieldref->{layer_fields} = \%taxproduct_fields;
1089     $fieldref->{layer_values_callback} = $taxproduct_values;
1090   }
1091 };
1092
1093 my $discountnum_hashref = {
1094                             'disabled' => '',
1095                             'months' => { 'op' => '>', 'value' => 1 },
1096                           };
1097
1098 </%init>