add conditions for customer cancelled packages, RT#42043
[freeside.git] / rt / share / html / Search / Schedule.html
1 <& /Elements/Header, Title => 'Schedule', JavaScript => 0 &>
2
3 <SCRIPT TYPE="text/javascript">
4
5   // sets cell content and bgcolor in a div, for use as a draggable
6   //   (draggable tds have border problems on FF/IE)
7   function set_cell_div ($cell,content,bgcolor) {
8     var $div = $cell.data('div');
9     if (!$div) {
10       $div = $(document.createElement('div'));
11       $div.data('cell',$cell);
12       $cell.data('div',$div);
13       $cell.append($div);
14     }
15     $div.css('width','100%');
16     $div.css('background-color', bgcolor);
17     $div.html(content || '&nbsp;');
18   }
19
20   // gives cell the appearance dictated by its data
21   function set_data_cell ($cell) {
22     $cell.css('border',  '1px solid #D7D7D7' );
23     $cell.css('background-color', $cell.data('bgcolor'));
24     set_cell_div($cell,$cell.data('content'),$cell.data('bgcolor'));
25   }
26
27   // sets cell data and appearance to schedulable
28   function set_schedulable_cell ($cell) {
29     $cell.data('bgcolor',  '#FFFFFF' );
30     $cell.data('ticketid', 0 );
31     $cell.data('length',   0 );
32     $cell.data('cells',    0 );
33     $cell.data('offset',   0 );
34     $cell.data('label',    '' );
35     $cell.data('content',  '' );
36     set_data_cell($cell);
37   }
38
39   // sets cell data and appearance as an appointment
40   function set_appointment_cell ($cell,ticketid,bgcolor,label,length,cells,offset) {
41     $cell.data('bgcolor',  bgcolor );
42     $cell.data('ticketid', ticketid );
43     $cell.data('length',   length );
44     $cell.data('cells',    cells );
45     $cell.data('offset',   offset );
46     $cell.data('label',  label );
47     $cell.data('content', '');
48     if ( offset == 0 ) { // first row
49       var title = 
50         label +
51         ' <A HREF="<%$RT::WebPath%>/Ticket/Display.html?id=' + ticketid + '" target="_blank">view</A> ' +
52         <% include('/elements/popup_link.html',
53              action=>$RT::WebPath.'/Ticket/ModifyCustomFieldsPopup.html?id=__MAGIC_TICKET_ID__',
54              label =>'edit',
55              actionlabel => 'Edit appointment',
56              height      => 436, # better: A + B * (num_custom_fields)
57           ) |n,js_string
58         %>;
59       title = title.replace( /__MAGIC_TICKET_ID__/, ticketid );
60       $cell.data('content', title);
61     }
62     set_data_cell($cell);
63   }
64
65 % if ( $cells ) {
66
67   // hover effects for scheduling new appointment
68
69   function boxon(what) {
70     var $this = $(what);
71     for ( var c=0; c < <%$cells%>; c++) {
72
73       $this.css('background-color', '#ffffdd');
74       set_cell_div($this,'','#ffffdd');
75       if ( c == 0 ) {
76         $this.css('border-top', '1px double black');
77       }
78       if ( c == <%$cells-1%> ) {
79         $this.css('border-bottom', '1px solid black');
80       }
81       $this.css('border-left', '1px double black');
82       $this.css('border-right', '1px solid black');
83
84       var rownum = $this.parent().prevAll('tr').length;
85       var colnum = $this.prevAll('td').length;
86       $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
87     }
88   }
89
90   function boxoff(what) {
91     var $this = $(what);
92     for ( var c=0; c < <%$cells%>; c++) {
93       $this.css('background-color', '#ffffff');
94       set_cell_div($this,'','#ffffff');
95       $this.css('border', '1px solid #D7D7D7'); //watch out in IE8 woes, empty string removes cell borders
96       var rownum = $this.parent().prevAll('tr').length;
97       var colnum = $this.prevAll('td').length;
98       $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
99     }
100   }
101
102
103 % } else {
104
105   // functions for drag-and-drop rescheduling
106
107   // ticket-dependant test if we can drop here
108   // prevent overlap with other appointments, while allowing appointment to overlap itself
109   function can_drop ($where, ui) {
110     var cells = ui.draggable.data('cell').data('cells');
111     var ticketid = ui.draggable.data('cell').data('ticketid');
112     for (var c=0; c < cells; c++) {
113       if (!$where.is('.ui-droppable')) {
114         return false;
115       }
116       if ($where.data('ticketid')) {
117         if ($where.data('ticketid') != ticketid) {
118           return false;
119         }
120         if ($where.data('offset') == c) { // don't reschedule in the same slot
121           return false;
122         }
123       }
124       var rownum = $where.parent().prevAll('tr').length;
125       var colnum = $where.prevAll('td').length;
126       $where = $where.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
127     }
128     return true;
129   }
130
131   // makes cell droppable (can reschedule here, subject to can_drop)
132   function set_droppable_cell ($cell) {
133     $cell.droppable({
134       over: appointment_drag_over,
135       drop: reschedule_appointment,
136       tolerance: 'pointer'
137     });
138   }
139
140   // makes cell draggable (able to be rescheduled)
141   function set_draggable_cell ($cell) {
142     var $div = $cell.data('div');
143     $div.draggable({
144       containment: '.titlebox-content',
145       revert: true,
146       revertDuration: 0,
147       start: appointment_drag_start,
148       stop: appointment_drag_stop,
149       zIndex: 10,
150     });
151   }
152
153   // gives cell a white (schedulable) appearance, without changing cell data
154   function set_white_cell ($cell) {
155     $cell.css('border',  '1px solid #D7D7D7' );
156     $cell.css('background-color', '#FFFFFF');
157     set_cell_div($cell,'','#FFFFFF');
158   }
159
160   // track drag highlighting
161   var drag_hi;
162
163   // clear drag highlighting
164   function clear_drag_hi (cells) {
165     if ( drag_hi ) {
166       for ( var c=0; c < cells; c++) {
167         if (drag_hi.data('isdragging')) {
168           drag_hi.css('border',  '1px solid #D7D7D7' );
169           drag_hi.css('background-color',  '#FFFFFF' );
170         } else {
171           set_white_cell(drag_hi);
172         }
173         var rownum = drag_hi.parent().prevAll('tr').length;
174         var colnum = drag_hi.prevAll('td').length;
175         drag_hi = drag_hi.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
176       }
177       drag_hi = undefined;
178     }
179   }
180
181   // drag start event
182   function appointment_drag_start(event, ui) {
183     var $this = $(this);
184     // cell that's dragging
185     $this = $this.data('cell');
186     set_cell_div($this,$this.data('label'),$this.data('bgcolor'));
187     $this.data('isdragging',true);
188     var offset = $this.data('offset');
189     var cells  = $this.data('cells');
190     // jump to first cell in appointment
191     var rownum = $this.parent().prevAll('tr').length;
192     var colnum = $this.prevAll('td').length;
193     $this = $this.parent().parent().children('tr').eq(rownum-offset).children('td').eq(colnum);
194     // loop through all cells in appointment
195     for ( var c=0; c < cells; c++) {
196       if ($this.data('isdragging')) {
197         $this.css('background-color', '#FFFFFF');
198       } else {
199         set_white_cell($this);
200       }
201       var rownum = $this.parent().prevAll('tr').length;
202       var colnum = $this.prevAll('td').length;
203       $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
204     }
205   }
206
207   // drag stop event
208   function appointment_drag_stop(event, ui) {
209     var $this = $(this);
210     // cell that's dragging
211     $this = $this.data('cell');
212     var cells = $this.data('cells');
213     clear_drag_hi(cells);
214     $this.data('isdragging',false);
215     var offset = $this.data('offset');
216     // jump to first cell in appointment
217     var rownum = $this.parent().prevAll('tr').length;
218     var colnum = $this.prevAll('td').length;
219     $this = $this.parent().parent().children('tr').eq(rownum-offset).children('td').eq(colnum);
220     // loop through all cells in appointment
221     for ( var c=0; c < cells; c++) {
222       set_data_cell($this);
223       var rownum = $this.parent().prevAll('tr').length;
224       var colnum = $this.prevAll('td').length;
225       $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
226     }
227   }
228
229   // drag over event
230   function appointment_drag_over(event, ui) {
231     // the cell that's dragging
232     var cells = ui.draggable.data('cell').data('cells');
233     // the droppable cell that you're over
234     var $this = $(this);
235     clear_drag_hi(cells);
236     if (!can_drop($this, ui)) return;
237     drag_hi = $this;
238     // loop through potential appointment cells
239     for ( var c=0; c < cells; c++) {
240       $this.css('background-color', '#ffffdd');
241       if ( !$this.data('isdragging')) {
242         set_cell_div($this,'','#ffffdd');
243       } else {
244         $this.css('background-color','#ffffdd');
245       }
246       if ( c == 0 ) {
247         $this.css('border-top', '1px double black');
248       }
249       if ( c == (cells-1) ) {
250         $this.css('border-bottom', '1px solid black');
251       }
252       $this.css('border-left', '1px double black');
253       $this.css('border-right', '1px solid black');
254       var rownum = $this.parent().prevAll('tr').length;
255       var colnum = $this.prevAll('td').length;
256       $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
257     }
258   }
259
260   // drop event
261   function reschedule_appointment( event, ui ) {
262
263         // the droppable cell that you're over
264     var $this = $(this);
265
266     if (!can_drop($this, ui)) return;
267
268 %   #get the ticket number and appointment length (from the draggable object)
269     var dragcell = ui.draggable.data('cell');
270     var ticketid = dragcell.data('ticketid');
271     var length   = dragcell.data('length');
272     var bgcolor  = dragcell.data('bgcolor');
273     var offset   = dragcell.data('offset');
274
275 %   #and.. the new date and time, and username (from the droppable object)
276     var starts   = $this.data('starts');
277     var username = $this.data('username');
278     var due = parseInt(starts) + parseInt(length);
279     var n_epoch        = $this.data('epoch');
280     var n_st_tod_row   = $this.data('tod_row');
281
282     var droppable = $this;
283     ui.draggable.effect( "transfer", { to: droppable }, 420 );
284
285 %   #tell the backend to reschedule it
286     var url = "<% popurl(3) %>misc/xmlhttp-ticket-update.html?" +
287               "id=" + ticketid + ";starts=" + starts + ";due=" + due +
288               ";username=" + username;
289
290     $.getJSON( url, function( data ) {
291       if ( data.error && data.error.length ) {
292 %       #error?  "that shouldn't happen" but should display 
293         alert(data.error);
294
295       } else {
296
297         var label = data.sched_label;
298
299         // jump to first cell in appointment
300         var rownum = dragcell.parent().prevAll('tr').length;
301         var colnum = dragcell.prevAll('td').length;
302         dragcell = dragcell.parent().parent().children('tr').eq(rownum-offset).children('td').eq(colnum);
303
304         // remove old appointment entirely
305         var epoch        = dragcell.data('epoch');
306         var st_tod_row   = dragcell.data('tod_row');
307         var old_username = dragcell.data('username');
308         var cells        = dragcell.data('cells');
309         for ( var c=0; c < cells; c++) {
310           var tod_row = parseInt(st_tod_row) + (c * <%$timestep%>);
311           var td_id = 'td_' + epoch +
312                       '_' + String( tod_row ) +
313                       '_' + old_username;
314           var $cell = $('#'+td_id);
315           $cell.data('div').draggable('destroy');
316           set_schedulable_cell($cell);
317           set_droppable_cell($cell);
318         }
319
320         // set appointment in new position
321         clear_drag_hi(cells);
322         for ( var d=0; d < cells; d++) {
323           var n_tod_row = parseInt(n_st_tod_row) + (d * <%$timestep%>);
324           var n_td_id = 'td_' + n_epoch +
325                         '_' + String( n_tod_row ) +
326                         '_' + username;
327           var $cell = $('#'+n_td_id);
328           set_appointment_cell($cell,ticketid,bgcolor,label,length,cells,d);
329           set_draggable_cell($cell);
330           set_droppable_cell($cell);
331         }
332       }
333     });
334   }
335
336 % } # end of rescheduling functions
337
338 </SCRIPT>
339
340 <& /Search/Calendar.html,
341      @_,
342      Query       => "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
343                      AND ( Type = 'reminder' OR 'Type' = 'ticket' )
344                      AND Queue = $queueid ",
345      slots       => scalar(@usernames),
346      Embed       => 'Schedule.html',
347      DimPast     => 1,
348      Display     => 'Schedule',
349      DisplayArgs => [ username  => \@usernames,
350                       LengthMin => $LengthMin,
351                       #oops, more freeside abstraction-leaking
352                       custnum   => $ARGS{custnum},
353                       pkgnum    => $ARGS{pkgnum},
354                       RedirectToBasics => $ARGS{RedirectToBasics},
355                     ],
356 &>
357
358 <%ONCE>
359
360 my $timestep =  RT->Config->Get('CalendarWeeklySizeMin') || 30; #1/2h
361
362 </%ONCE>
363 <%init>
364
365 #abstraction-leaking
366 my $conf = new FS::Conf;
367 my $queueid = $conf->config('ticket_system-appointment-queueid')
368   or die "ticket_system-appointment-queueid configuration not set";
369
370 my @files = ();
371 #if ( ! $initialized ) {
372   push @files, map "overlibmws$_", ( '', qw( _iframe _draggable _crossframe ) );
373   push @files, map { "${_}contentmws" } qw( iframe ajax );
374 #%}
375
376 my @usernames = ();
377 if ( ref($ARGS{username}) ) {
378   @usernames = @{ $ARGS{username} };
379 } elsif ( $ARGS{username} ) {
380   @usernames = ( $ARGS{username} );
381 } else {
382   #look them up ourslves... again, more FS abstraction-leaking, but 
383   # we want to link to the schedule view, and better than doing this every
384   # menu render
385   use FS::Record qw( qsearch );
386   use FS::sched_item;
387   my @sched_item = qsearch('sched_item', { 'disabled' => '', });
388   @usernames = map $_->access_user->username, @sched_item;
389 }
390
391 ( my $LengthMin = $ARGS{LengthMin} ) =~ /^\d+$/ or die 'non-numeric LengthMin';
392
393 my $cells = int($LengthMin / $timestep);
394 $cells++ if $LengthMin % $timestep;
395
396 </%init>