Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / httemplate / edit / part_event.html
1 <% include( 'elements/edit.html',
2               'name'   => 'Billing event definition',
3               'table'  => 'part_event',
4               'fields' => [
5                             'event',
6                             { field   => 'eventtable',
7                               type    => 'select',
8                               options => [ FS::part_event->eventtables ],
9                               labels  => $eventtable_labels,
10                               onchange => 'eventtable_changed',
11                             },
12                             { field   => 'agentnum',
13                               type    => 'select-agent',
14                               disable_empty => $disable_empty_agent,
15                             },
16                             { field   => 'check_freq',
17                               type    => 'select',
18                               options => [ '1d', '1m' ],
19                               labels  => $check_freq_labels,
20                             },
21                             { field   => 'disabled',
22                               type    => 'checkbox',
23                               value   => 'Y',
24                             },
25                             { field   => '_initialize',
26                               type      => 'checkbox',
27                               onchange  => '_initialize_changed',
28                               value   => 'Y',
29                             }, 
30                             { type    => 'title',
31                               value   => 'Event Conditions',
32                             },
33                             { field   => 'conditionname',
34                               type    => 'selectlayers',
35                               options => [ keys %all_conditions ],
36                               labels  => \%condition_labels,
37                               onchange => 'condition_changed(what);',
38                               layer_fields => \%condition_fields,
39                               layer_values_callback => $condition_layer_values,
40                               html_between       => n_a('action'),
41                               m2name_table       => 'part_event_condition',
42                               m2name_namecol     => 'conditionname',
43                               m2_label           => 'Condition',
44                               m2_new_default     => \@implicit_condition_objs,
45                               m2_error_callback  => $condition_error_callback,
46                               m2_remove_warnings => \%condition_remove_warnings,
47                               m2_new_js          => 'condition_repop',
48                               m2_remove_js       => 'condition_add',
49                             },
50                             { type    => 'title',
51                               value   => 'Event Action',
52                             },
53                             { field   => 'action',
54                               type     => 'selectlayers',
55                               options  => [ keys %all_actions ],
56                               labels   => \%action_labels,
57                               onchange => 'action_changed(what);',
58                               layer_fields => \%action_fields,
59                               layer_values_callback => $action_layer_values,
60                               html_between => n_a('action'),
61                             },
62                           ],
63               'labels' => {
64                             'eventpart'  => 'Event',
65                             'event'      => 'Event name',
66                             'eventtable' => 'Type',
67                             'agentnum'   => 'Agent',
68                             'check_freq' => 'Check frequency',
69                             'disabled'   => 'Disable event',
70
71                             'conditionname' => 'Add&nbsp;new&nbsp;condition',
72                             #'weight',
73                             'action'     => 'Action',
74                             '_initialize' => 'Initialize event',
75                           },
76               'viewall_dir' => 'browse',
77               'new_callback' => sub { #start empty for new events only
78                 my( $cgi, $object, $fields_listref ) = @_; 
79                 unshift @{ $fields_listref->[1]{'options'} }, '';
80               },
81               'error_callback' => $error_callback,
82
83               'agent_virt'       => 1,
84               'agent_null_right' => 'Edit global billing events',
85           )
86 %>
87 <SCRIPT TYPE="text/javascript">
88
89   window.onload = function () { eventtable_changed(document.getElementById('eventtable')) };
90   var notonload = 0;
91
92   function eventtable_changed(what) {
93
94 %   if ( $JS_DEBUG ) {
95       alert('eventtable_changed called on ' + what );
96 %   }
97
98     var eventtable = what.options[what.selectedIndex].value;
99 %   if ( $JS_DEBUG ) {
100       alert ("eventtable: " + eventtable);
101 %   }
102     var eventdesc  = what.options[what.selectedIndex].text;
103
104     //remove the ** Select type **
105     if ( what.options[0].value == '' && notonload++ > 0 ) {
106       what.options[0] = null;
107     }
108
109     ////
110     // XXX gray out conditions that can't apply (in addition to the warning)?
111     ////
112
113     ////
114     // update condition selects
115     ////
116
117     for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
118       var cond_id = 'conditionname' + cnum;
119       var cond_select = document.getElementById(cond_id);
120
121 %     if ( $JS_DEBUG ) {
122         alert('updating ' + cond_id);
123 %     }
124
125       // save off the current value
126       var conditionname = cond_select.options[cond_select.selectedIndex].value;
127       var cond_desc     = cond_select.options[cond_select.selectedIndex].text;
128
129       var seen_condition = condition_repop(cond_select);
130
131       var warning = document.getElementById(cond_id + '_warning');
132 %     if ( $JS_DEBUG ) {
133         alert('turning off warning; setting style.display of '+ cond_id +
134               '_warning (' + warning + ') to none');
135 %     }
136       warning.style.display = 'none';
137
138       if ( ! seen_condition && conditionname != '' ) {
139         // add the current (not valid) condition back
140         opt(cond_select, conditionname, cond_desc, true );
141         if ( ! condition_is_implicit(conditionname) ) {
142           cond_select.parentNode.parentNode.style.display = '';
143           cond_select.disabled = '';
144           // turn on a warning and gray out the condition row
145 %         if ( $JS_DEBUG ) {
146             alert('turning on warning; setting style.display of '+ cond_id +
147                   '_warning (' + warning + ') to ""');
148 %         }
149           warning.innerHTML = 'Not applicable to ' + eventdesc + ' events';
150           warning.style.display = '';
151         } else {
152           if ( ! condition_in_eventtable(conditionname) ) {
153 %           if ( $JS_DEBUG ) {
154               alert(conditionname + " not in " + eventtable + "; disabling");
155 %           }
156             cond_select.parentNode.parentNode.style.display = 'none';
157             cond_select.disabled = 'disabled';
158           } else {
159 %           if ( $JS_DEBUG ) {
160               alert(conditionname + " implicit for " + eventtable + "; enabling");
161 %           }
162             cond_select.parentNode.parentNode.style.display = '';
163             cond_select.disabled = '';
164           }
165         }
166       }
167
168     }
169
170
171     ////
172     // update action select
173     ////
174
175     // save off the current value first!!
176     var action = what.form.action.options[what.form.action.selectedIndex].value;
177     var a_desc = what.form.action.options[what.form.action.selectedIndex].text;
178     var seen_action = false;
179
180     // blank the current action select
181     for ( var i = what.form.action.length; i >= 0; i-- )
182       what.form.action.options[i] = null;
183
184     if ( action == '' ) {
185       opt(what.form.action, action, a_desc, true );
186     }
187
188     // repopulate it
189 %   foreach my $eventtable ( FS::part_event->eventtables ) {
190 %     tie my %actions, 'Tie::IxHash', FS::part_event->actions($eventtable);
191 %     #use Data::Dumper; warn Dumper(%actions);
192
193       if ( eventtable == '<% $eventtable %>' ) {
194
195 %       foreach my $action ( keys %actions ) {
196 %         ( my $description = $actions{$action}->{'description'} ) =~ s/'/\\'/g;
197
198           var sel = false;
199           if ( action == '<% $action %>' ) {
200             seen_action = true;
201             sel = true;
202           }
203           opt( what.form.action, '<% $action %>', '<% $description %>', sel );
204 %       }
205
206       }
207
208 %   }
209
210     // by default, turn off warnings and enable the submit button
211     var warning = document.getElementById('action_warning');
212     warning.style.display = 'none';
213     var submit_button = document.getElementById('submit');
214     submit_button.disabled = '';
215
216     if ( ! seen_action && action != '' ) {
217       // add the current (not valid) action back
218       opt( what.form.action, action, a_desc, true );
219       // turn on a warning and disable the submit button
220       //warning.innerHTML = a_desc + ' event not available as a ' +
221       warning.innerHTML = 'Not available as a ' + eventdesc + ' action';
222       warning.style.display = '';
223       submit_button.disabled = 'disabled';
224     }
225
226   }
227
228   function opt(what,value,text,selected) {
229     var optionName = new Option(text, value, false, selected);
230     var length = what.length;
231     what.options[length] = optionName;
232   }
233
234   function action_changed(what) {
235     // remove '** Select new **'
236     if ( what.options[0].value == '' ) {
237        what.options[0] = null;
238     }
239     // remove the warning, remove the invalid action, enable the submit button
240     var warning = document.getElementById('action_warning');
241     if ( warning.style.display == '' ) {
242       warning.style.display = 'none';
243       what.options[what.length-1] = null;
244       document.getElementById('submit').disabled = '';
245     }
246   }
247
248   function condition_changed(what) {
249     // remove '** Select new **'
250     if ( what.options[0].value == '' ) {
251        what.options[0] = null;
252     }
253
254     var previousValue = what.getAttribute('previousValue');
255     var previousText = what.getAttribute('previousText');
256     var value = what.options[what.selectedIndex].value;
257     var text = what.options[what.selectedIndex].text;
258
259 %   foreach my $value ( keys %condition_remove_warnings ) {
260       if ( previousValue == '<% $value %>' ) {
261         if ( !confirm( <% $condition_remove_warnings{$value} |js_string %> ) ) {
262           for ( var i=0; i < what.length; i++ ) {
263             if ( what.options[i].value == previousValue ) {
264               what.selectedIndex = i;
265             }
266           }
267           return false;
268         }
269       }
270 %   }
271
272     //alert(previous + ' changed to ' + value);
273
274     var field_regex = /(\d+)$/;
275     var match = field_regex.exec(what.name);
276     if ( !match ) {
277       alert(what.name + " didn't match?!");
278       return;
279     }
280
281     //add the previous condition *back* to all the other selects...
282     condition_add(previousValue, previousText, match[1]);
283
284     what.setAttribute('previousValue', value);
285     what.setAttribute('previousText', text);
286
287     // remove the new condition from all other selects
288     condition_remove(value, match[1]);
289
290   }
291
292   function condition_avail(check_cond, curnum) {
293     for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
294       if ( cnum == curnum ) continue;
295
296       var cond_id = 'conditionname' + cnum;
297       var cond_select = document.getElementById(cond_id);
298
299       //alert("checking " + cond_id + " (" + cond_select.disabled + ")");
300
301       if ( cond_select.disabled ) continue;
302
303       // the current value
304       var conditionname = cond_select.options[cond_select.selectedIndex].value;
305
306       if ( check_cond == conditionname ) return false;
307
308     }
309
310     return true;
311
312   }
313
314   function condition_remove(remove_cond, curnum) {
315
316     if ( remove_cond.length == 0 ) return;
317
318     for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
319       if ( cnum == curnum ) continue;
320
321       var cond_id = 'conditionname' + cnum;
322       var cond_select = document.getElementById(cond_id);
323
324       //for ( var i = cond_select.length; i >= 0; i-- ) {
325       for ( var i=0; i < cond_select.length; i++ ) {
326         if ( cond_select.options[i].value == remove_cond ) {
327           cond_select.options[i] = null;
328         }
329       }
330
331     }
332
333   }
334
335   function condition_add(add_condname, add_conddesc, curnum) {
336
337     if ( add_condname.length == 0 ) return;
338
339     var in_eventtable = condition_in_eventtable(add_condname);
340
341     if ( ! in_eventtable ) return;
342
343     for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
344       if ( cnum == curnum ) continue;
345
346       var cond_id = 'conditionname' + cnum;
347       var cond_select = document.getElementById(cond_id);
348
349       if ( cond_select.disabled ) continue;
350
351       //alert("adding " + add_condname + " to " + cond_id);
352
353       opt(cond_select, add_condname, add_conddesc, false );
354
355       cond_select.parentNode.parentNode.style.display = '';
356
357     }
358
359   }
360
361   function condition_in_eventtable(condname) {
362
363     var eventtable_el = document.getElementById('eventtable');
364     var eventtable = eventtable_el.options[eventtable_el.selectedIndex].value;
365
366     var in_eventtable = false;
367
368 %   foreach my $eventtable ( FS::part_event->eventtables ) {
369 %     tie my %conditions, 'Tie::IxHash',
370 %       FS::part_event_condition->conditions($eventtable);
371
372       if ( eventtable == '<% $eventtable %>' ) {
373
374 %       foreach my $conditionname ( keys %conditions ) {
375
376           if ( condname == '<% $conditionname %>' ) {
377             in_eventtable = true;
378           }
379
380 %       }
381
382       }
383
384 %   }
385
386     return in_eventtable;
387
388   }
389
390   function condition_is_implicit(condname) {
391
392     if ( true <% @implicit_conditions
393                    ? ( ' && '. join(' && ', map { "condname != '$_'" }
394                                                 @implicit_conditions
395                                    )
396                      )
397                    : ''
398               %> ) {
399       return false;
400     } else {
401       return true;
402     }
403   }
404
405   function condition_repop(cond_select) {
406
407     var eventtable_el = document.getElementById('eventtable');
408     var eventtable = eventtable_el.options[eventtable_el.selectedIndex].value;
409
410     // save off the current value
411     var conditionname = cond_select.options[cond_select.selectedIndex].value;
412     var cond_desc     = cond_select.options[cond_select.selectedIndex].text;
413     var seen_condition = false;
414
415     //skip deleted conditions
416     if ( cond_select.disabled && conditionname != '' && ! condition_is_implicit(conditionname) ) {
417       return false;
418     }
419
420     var field_regex = /(\d+)$/;
421     var match = field_regex.exec(cond_select.name);
422     if ( !match ) {
423       alert(what.name + " didn't match?!");
424       return;
425     }
426     var cnum = match[1];
427
428     // blank the current condition select
429     for ( var i = cond_select.length; i >= 0; i-- )
430       cond_select.options[i] = null;
431
432     if ( conditionname == '' ) {
433       opt(cond_select, conditionname, cond_desc, true );
434     }
435
436     // repopulate it
437 %   foreach my $eventtable ( FS::part_event->eventtables ) {
438 %     tie my %conditions, 'Tie::IxHash',
439 %       FS::part_event_condition->conditions($eventtable);
440
441       if ( eventtable == '<% $eventtable %>' ) {
442
443 %       foreach my $conditionname ( keys %conditions ) {
444 %         my $description = $conditions{$conditionname}->{'description'};
445 %         $description =~ s/'/\\'/g;
446
447           var sel = false;
448           if ( conditionname == '<% $conditionname %>' ) {
449             seen_condition = true;
450             sel = true;
451           }
452
453           if ( condition_avail("<% $conditionname %>", cnum) ) {
454             opt(cond_select, '<% $conditionname %>', '<% $description %>', sel);
455           }
456
457 %       }
458
459       }
460         
461 %   }
462
463     if ( cond_select.length > 1 || cond_select.length == 1 && cond_select.options[0].value.length > 0 ) {
464        
465       cond_select.parentNode.parentNode.style.display = '';
466       cond_select.disabled = '';
467
468     } else {
469       cond_select.parentNode.parentNode.style.display = 'none';
470       cond_select.disabled = 'disabled';
471     }
472
473     return seen_condition;
474
475   }
476
477   function _initialize_changed(what) {
478     document.getElementById('disabled').disabled = what.checked;
479     if ( what.checked ) {
480 %# because it's not an immediately obvious concept
481       alert('Initializing the event will treat it as "already run" on the '+
482             'current date for all existing customers.  This affects '+
483             'conditions that prevent running an event more than once.');
484     }
485   }
486
487 </SCRIPT>
488 <%once>
489
490 #misc (eventtable, check_freq)
491
492 my $eventtable_labels = FS::part_event->eventtable_labels;
493 $eventtable_labels->{''} = '** Select type **';
494
495 my $check_freq_labels = FS::part_event->check_freq_labels;
496
497 #conditions
498
499 tie my %all_conditions, 'Tie::IxHash', 
500   '' => { 'description' => '*** Select new condition ***', },
501   FS::part_event_condition->conditions();
502
503 # *** Select new condition *** sorts to the beginning anyway
504 (tied %all_conditions)->SortByValue;
505
506 my %condition_labels = map { $_ => $all_conditions{$_}->{'description'} }
507                            keys %all_conditions;
508
509 #my %condition_fields = map { $_ => $all_conditions{$_}->{option_fields} } 
510 #                           keys %all_conditions;
511 my %condition_fields = map { my $c = $_;
512                              tie my %opts, 'Tie::IxHash',
513                                @{ $all_conditions{$c}->{'option_fields'} || []};
514                              %opts = ( map { ( "$c.$_" => $opts{$_} ); }
515                                            keys %opts
516                                      );
517                              ( $c => [ %opts ] );
518                            } 
519                            keys %all_conditions;
520
521 my @implicit_conditions = sort { $all_conditions{$a}->{'implicit_flag'} <=>
522                                  $all_conditions{$b}->{'implicit_flag'}
523                                }
524                           grep { $all_conditions{$_}->{'implicit_flag'} }
525                           keys %all_conditions;
526
527 my @implicit_condition_objs = map {
528                                     new FS::part_event_condition {
529                                       'conditionname' => $_,
530                                     };
531                                   }
532                                   @implicit_conditions;
533
534 my %condition_remove_warnings =
535   map  { ( $_ => $all_conditions{$_}->{'remove_warning'} ); }
536   grep { $all_conditions{$_}->{'remove_warning'} }
537   keys %all_conditions;
538
539 #actions
540
541 tie my %all_actions, 'Tie::IxHash', 
542   '' => { 'description' => '*** Select event action ***', },
543   FS::part_event->actions();
544
545 (tied %all_actions)->SortByValue;
546
547 my %action_labels = map { $_ => $all_actions{$_}->{'description'} }
548                         keys %all_actions;
549
550 #my %action_fields = map { $_ => $all_actions{$_}->{option_fields} }
551 #                        keys %all_actions;
552 my %action_fields = map { my $action = $_;
553                           tie my %opts, 'Tie::IxHash',
554                             @{ $all_actions{$action}->{option_fields} || [] };
555                           %opts = ( map { ( "$action.$_" => $opts{$_} ); }
556                                         keys %opts
557                                   );
558                           ( $action => [ %opts ] );
559                         }
560                         keys %all_actions;
561
562 #subs
563
564 sub n_a {
565   my $t = shift;
566
567   return sub {
568     my $field = shift;
569     qq( <FONT ID="${field}_warning" STYLE="display:none" COLOR="#FF0000">).
570       "Party Party Join us Join us".
571       '</FONT>';
572   };
573 }
574
575 my $action_layer_values = sub {
576   my( $cgi, $part_event ) = @_;
577   my $action = $cgi->param('action') || $part_event->action;
578   return {} unless $action;
579   scalar( #force hashref
580     {
581       #map { $_ => { $part_event->options } }
582       #    keys %action_fields
583       map { my $action = $_;
584             my %fields = @{ $action_fields{$action} };
585             my %obj_opts = $part_event->options;
586             %obj_opts = map { ( "$action.$_" => $obj_opts{$_} ); }
587                             keys %obj_opts;
588             my %opts =
589               map { #false laziness w/process/part_event.html
590                     my $option = $_;
591                     my $value = scalar($cgi->param($_)) || $obj_opts{$_};
592
593                     if ( $option =~ /^(.*)\.reasonnum$/ && $value == -1 ) {
594                       $value = {
595                         'typenum' => scalar( $cgi->param( "new${option}T" ) ),
596                         'reason'  => scalar( $cgi->param( "new${option}"  ) ),
597                       };
598                     }
599
600                     ( $option => $value );
601
602                   }
603                   keys %fields;
604             ( $action => \%opts );
605           }
606           keys %action_fields
607     }
608   );
609 };
610
611 tie my %cgi_conditions, 'Tie::IxHash';
612
613 my $error_callback = sub {
614   my( $cgi, $object, $fields_listref ) = @_;
615
616   my @cond_params = grep /^conditionname\d+$/, $cgi->param;
617
618   %cgi_conditions = map {
619     my $param = $_;
620     my $conditionname = $cgi->param($param);
621     $conditionname => {
622       map { 
623
624         my $cgi_key = $_;
625         $cgi_key =~ /^$param\.$conditionname\.(.*)$/ or die 'wtf!';
626         my $key = $1;
627         #my $value = $cgi->param($_);
628
629         #my $info = $all_conditions->{$conditionname}
630         my %cond_opts =
631           @{ $all_conditions{$conditionname}->{'option_fields'} || []};
632         my $info = $cond_opts{$key};
633
634         my $value;
635         #false laziness w/process/part_event.html
636         if (      $info->{'type'} =~ /^(select|checkbox)-?multiple$/
637                or $info->{'type'} =~ /^select/ && $info->{'multiple'} ) {
638           $value = { map { $_ => 1 } $cgi->param($cgi_key) };
639         } elsif ( $info->{'type'} eq 'freq' ) {
640           $value = $cgi->param($cgi_key). $cgi->param($cgi_key.'_units');
641         } else {
642           $value = $cgi->param($cgi_key);
643         }
644
645         $key => $value;
646
647       } grep /^$param\.$conditionname\./, $cgi->param
648     };
649   } grep $cgi->param($_), grep /^conditionname\d+$/, $cgi->param;
650
651 };
652
653 my $condition_error_callback = sub {
654   map {
655     new FS::part_event_condition { 'conditionname' => $_, };
656   } keys %cgi_conditions;
657 };
658
659 my $condition_layer_values = sub {
660   #m2_table option causes this to be
661   # part_event_condition instead of part_event
662   my ( $cgi, $part_event_condition, $switches ) = @_;
663   scalar( #force hashref
664     {
665       #map { $_ => { $part_event_condition->options } }
666       #    keys %condition_fields
667       map { my $conditionname = $_;
668             my %opts = $switches->{'mode'} eq 'error'
669                        ? %{ $cgi_conditions{$conditionname} || {} }
670                        : $part_event_condition->options;
671             %opts = (
672               map { ( "$conditionname.$_" => $opts{$_} ); }
673                   keys %opts
674             );
675             ( $conditionname => \%opts );
676           }
677           keys %condition_fields
678     }
679   );
680 };
681
682
683 </%once>
684 <%init>
685
686 my $curuser = $FS::CurrentUser::CurrentUser;
687
688 die "access denied"
689   unless $curuser->access_right('Edit billing events')
690       || $curuser->access_right('Edit global billing events');
691
692 my $disable_empty_agent= ! $curuser->access_right('Edit global billing events');
693
694 %cgi_conditions = ();
695 my $use_cgi_conditions = 0;
696
697 my $JS_DEBUG = 0;
698
699 </%init>