Add the ability to link customer service definition fields to inventory
[freeside.git] / httemplate / edit / part_svc.cgi
1 <%
2 my $part_svc;
3 my $clone = '';
4 if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {#clone
5   #$cgi->param('clone') =~ /^(\d+)$/ or die "malformed query: $query";
6   $part_svc = qsearchs('part_svc', { 'svcpart'=>$1 } )
7     or die "unknown svcpart: $1";
8   $clone = $part_svc->svcpart;
9   $part_svc->svcpart('');
10 } elsif ( $cgi->keywords ) { #edit
11   my($query) = $cgi->keywords;
12   $query =~ /^(\d+)$/ or die "malformed query: $query";
13   $part_svc=qsearchs('part_svc', { 'svcpart'=>$1 } )
14     or die "unknown svcpart: $1";
15 } else { #adding
16   $part_svc = new FS::part_svc {};
17 }
18
19 my $action = $part_svc->svcpart ? 'Edit' : 'Add';
20 my $hashref = $part_svc->hashref;
21 #   my $p_svcdb = $part_svc->svcdb || 'svc_acct';
22
23
24            #" onLoad=\"visualize()\""
25 %>
26 <%= include("/elements/header.html","$action Service Definition",
27            menubar( 'Main Menu'         => $p,
28                     'View all service definitions' => "${p}browse/part_svc.cgi"
29                   ),
30            )
31 %>
32
33 <FORM NAME="dummy">
34
35       Service Part #<%= $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %>
36 <BR><BR>
37 Service  <INPUT TYPE="text" NAME="svc" VALUE="<%= $hashref->{svc} %>"><BR>
38 Disable new orders <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<%= $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>><BR>
39 <INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $hashref->{svcpart} %>">
40 <BR>
41 Service definitions are the templates for items you offer to your customers.
42 <UL><LI>svc_acct - Accounts - anything with a username (Mailboxes, PPP accounts, shell accounts, etc.)
43     <LI>svc_domain - Domains
44     <LI>svc_forward - mail forwarding
45     <LI>svc_www - Virtual domain website
46     <LI>svc_broadband - Broadband/High-speed Internet service (always-on)
47     <LI>svc_external - Externally-tracked service
48 <!--   <LI>svc_charge - One-time charges (Partially unimplemented)
49        <LI>svc_wo - Work orders (Partially unimplemented)
50 -->
51 </UL>
52 For the selected table, you can give fields default or fixed (unchangable)
53 values, or select an inventory class to manually or automatically fill in
54 that field.
55 <BR><BR>
56
57 <%
58
59 #these might belong somewhere else for other user interfaces 
60 #pry need to eventually create stuff that's shared amount UIs
61 my $conf = new FS::Conf;
62 my %defs = (
63   'svc_acct' => {
64     'dir'       => 'Home directory',
65     'uid'       => 'UID (set to fixed and blank for no UIDs)',
66     'slipip'    => 'IP address',
67 #    'popnum'    => qq!<A HREF="$p/browse/svc_acct_pop.cgi/">POP number</A>!,
68     'popnum'    => {
69                      desc => 'Access number',
70                      type => 'select',
71                      select_table => 'svc_acct_pop',
72                      select_key   => 'popnum',
73                      select_label => 'city',
74                    },
75     'username'  => {
76                      desc => 'Username',
77                      type => 'text',
78                      disable_default => 1,
79                      disable_fixed => 1,
80                    },
81     'quota'     => { 
82                      desc => '',
83                      type => 'text',
84                      disable_inventory => 1,
85                    },
86     '_password' => 'Password',
87     'gid'       => 'GID (when blank, defaults to UID)',
88     'shell'     => {
89                      #desc =>'Shell (all service definitions should have a default or fixed shell that is present in the <b>shells</b> configuration file, set to blank for no shell tracking)',
90                      desc =>'Shell ( set to blank for no shell tracking)',
91                      type =>'select',
92                      select_list => [ $conf->config('shells') ],
93                      disable_inventory => 1,
94                    },
95     'finger'    => 'Real name (GECOS)',
96     'domsvc'    => {
97                      desc =>'svcnum from svc_domain',
98                      type =>'select',
99                      select_table => 'svc_domain',
100                      select_key   => 'svcnum',
101                      select_label => 'domain',
102                      disable_inventory => 1,
103                    },
104     'usergroup' => {
105                      desc =>'RADIUS groups',
106                      type =>'radius_usergroup_selector',
107                      disable_inventory => 1,
108                    },
109     'seconds'   => { desc => '',
110                      type => 'text',
111                      disable_inventory => 1,
112                    },
113   },
114   'svc_domain' => {
115     'domain'    => 'Domain',
116   },
117   'svc_forward' => {
118     'srcsvc'    => 'service from which mail is to be forwarded',
119     'dstsvc'    => 'service to which mail is to be forwarded',
120     'dst'       => 'someone@another.domain.com to use when dstsvc is 0',
121   },
122 #  'svc_charge' => {
123 #    'amount'    => 'amount',
124 #  },
125 #  'svc_wo' => {
126 #    'worker'    => 'Worker',
127 #    '_date'      => 'Date',
128 #  },
129   'svc_www' => {
130     #'recnum' => '',
131     #'usersvc' => '',
132   },
133   'svc_broadband' => {
134     'speed_down' => 'Maximum download speed for this service in Kbps.  0 denotes unlimited.',
135     'speed_up' => 'Maximum upload speed for this service in Kbps.  0 denotes unlimited.',
136     'ip_addr' => 'IP address.  Leave blank for automatic assignment.',
137     'blocknum' => 'Address block.',
138   },
139   'svc_external' => {
140     #'id' => '',
141     #'title' => '',
142   },
143 );
144
145   my %vfields;
146   foreach my $svcdb (grep dbdef->table($_), keys %defs ) {
147     my $self = "FS::$svcdb"->new;
148     $vfields{$svcdb} = {};
149     foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them
150       my $pvf = $self->pvf($field);
151       my @list = $pvf->list;
152       if (scalar @list) {
153         $defs{$svcdb}->{$field} = { desc        => $pvf->label,
154                                     type        => 'select',
155                                     select_list => \@list };
156       } else {
157         $defs{$svcdb}->{$field} = $pvf->label;
158       } #endif
159       $vfields{$svcdb}->{$field} = $pvf;
160       warn "\$vfields{$svcdb}->{$field} = $pvf";
161     } #next $field
162   } #next $svcdb
163
164   #code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm
165   # and generalize the subs
166   # condition sub is tested to see whether to disable display of this choice
167   # params: ( $def, $layer, $field )  (see SUB below)
168   my $inv_sub = sub {
169     ref($_[0]) && (    $_[0]->{disable_inventory} 
170                     || $_[0]->{'type'} ne 'text'  )
171   };
172   tie my %flag, 'Tie::IxHash',
173     ''  => { 'desc' => 'No default', },
174     'D' => { 'desc' => 'Default',
175              'condition' =>
176                sub { ref($_[0]) && $_[0]->{disable_default} }, 
177            },
178     'F' => { 'desc' => 'Fixed (unchangeable)',
179              'condition' =>
180                sub { ref($_[0]) && $_[0]->{disable_fixed} }, 
181            },
182     'M' => { 'desc' => 'Manual selection from inventory',
183              'condition' => $inv_sub,
184            },
185     'A' => { 'desc' => 'Automatically fill in from inventory',
186              'condition' => $inv_sub,
187            },
188     'X' => { 'desc' => 'Excluded',
189              'condition' =>
190                sub { ! $vfields{$_[1]}->{$_[2]} },
191
192            },
193   ;
194   
195   my @dbs = $hashref->{svcdb}
196              ? ( $hashref->{svcdb} )
197              : qw( svc_acct svc_domain svc_forward svc_www svc_broadband svc_external );
198
199   tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs;
200   my $widget = new HTML::Widgets::SelectLayers(
201     #'selected_layer' => $p_svcdb,
202     'selected_layer' => $hashref->{svcdb} || 'svc_acct',
203     'options'        => \%svcdb,
204     'form_name'      => 'dummy',
205     #'form_action'    => 'process/part_svc.cgi',
206     'form_action'    => 'part_svc.cgi', #self
207     'form_text'      => [ qw( svc svcpart ) ],
208     'form_checkbox'  => [ 'disabled' ],
209     'layer_callback' => sub {
210       my $layer = shift;
211       
212       my $html = qq!<INPUT TYPE="hidden" NAME="svcdb" VALUE="$layer">!;
213
214       my $columns = 3;
215       my $count = 0;
216       my @part_export =
217         map { qsearch( 'part_export', {exporttype => $_ } ) }
218           keys %{FS::part_export::export_info($layer)};
219       $html .= '<BR><BR>'. table(). 
220                "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>";
221       foreach my $part_export ( @part_export ) {
222         $html .= '<TD><INPUT TYPE="checkbox"'.
223                  ' NAME="exportnum'. $part_export->exportnum. '"  VALUE="1" ';
224         $html .= 'CHECKED'
225           if ( $clone || $part_svc->svcpart ) #null svcpart search causing error
226               && qsearchs( 'export_svc', {
227                                    exportnum => $part_export->exportnum,
228                                    svcpart   => $clone || $part_svc->svcpart });
229         $html .= '>'. $part_export->exportnum. ': '. $part_export->exporttype.
230                  ' to '. $part_export->machine. '</TD>';
231         $count++;
232         $html .= '</TR><TR>' unless $count % $columns;
233       }
234       $html .= '</TR></TABLE><BR><BR>';
235
236       $html .= include('/elements/table-grid.html', 'cellpadding' => 4 ).
237                '<TR>'.
238                  '<TH CLASS="grid" BGCOLOR="#cccccc">Field</TH>'.
239                  '<TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>'.
240                '</TR>';
241
242       my $bgcolor1 = '#eeeeee';
243       my $bgcolor2 = '#ffffff';
244       my $bgcolor;
245
246       #yucky kludge
247       my @fields = defined( dbdef->table($layer) )
248                       ? grep { $_ ne 'svcnum' } fields($layer)
249                       : ();
250       push @fields, 'usergroup' if $layer eq 'svc_acct'; #kludge
251       $part_svc->svcpart($clone) if $clone; #haha, undone below
252
253
254       foreach my $field (@fields) {
255
256         my $part_svc_column = $part_svc->part_svc_column($field);
257         my $value = $part_svc_column->columnvalue;
258         my $flag = $part_svc_column->columnflag;
259         my $def = $defs{$layer}{$field};
260         my $desc = ref($def) ? $def->{desc} : $def;
261
262         if ( $bgcolor eq $bgcolor1 ) {
263           $bgcolor = $bgcolor2;
264         } else {
265           $bgcolor = $bgcolor1;
266         }
267         
268         $html .= qq!<TR><TD CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">!.
269                  $field;
270         $html .= "- <FONT SIZE=-1>$desc</FONT>" if $desc;
271         $html .=  "</TD>";
272         $flag = '' if ref($def) && $def->{type} eq 'disabled';
273
274         $html .= qq!<TD CLASS="grid" BGCOLOR="$bgcolor">!;
275
276         if ( ref($def) && $def->{type} eq 'disabled' ) {
277         
278           $html .= 'No default';
279
280         } else {
281
282           $html .= qq!<SELECT NAME="${layer}__${field}_flag"!.
283                       qq! onChange="${layer}__${field}_flag_changed(this)">!;
284
285           foreach my $f ( keys %flag ) {
286
287             #here is where the SUB from above is called, to skip some choices
288             next if $flag{$f}->{condition}
289                  && &{ $flag{$f}->{condition} }( $def, $layer, $field );
290
291             $html .= qq!<OPTION VALUE="$f"!.
292                      ' SELECTED'x($flag eq $f ).
293                      '>'. $flag{$f}->{desc};
294
295           }
296
297           $html .= '</SELECT>';
298
299           $html .= join("\n",
300             '<SCRIPT>',
301             "  function ${layer}__${field}_flag_changed(what) {",
302             '    var f = what.options[what.selectedIndex].value;',
303             '    if ( f == "" || f == "X" ) { //disable',
304             "      what.form.${layer}__${field}.disabled = true;".
305             "      what.form.${layer}__${field}.style.backgroundColor = '#dddddd';".
306             "      if ( what.form.${layer}__${field}_classnum ) {".
307             "        what.form.${layer}__${field}_classnum.disabled = true;".
308             "        what.form.${layer}__${field}_classnum.style.backgroundColor = '#dddddd';".
309             "      }".
310             '    } else if ( f == "D" || f == "F" ) { //enable, text box',
311             "      what.form.${layer}__${field}.disabled = false;".
312             "      what.form.${layer}__${field}.style.backgroundColor = '#ffffff';".
313             "      what.form.${layer}__${field}.style.display = '';".
314             "      if ( what.form.${layer}__${field}_classnum ) {".
315             "        what.form.${layer}__${field}_classnum.disabled = false;".
316             "        what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';".
317             "        what.form.${layer}__${field}_classnum.style.display = 'none';".
318             "      }".
319             '    } else if ( f == "M" || f == "A" ) { //enable, inventory',
320             "      what.form.${layer}__${field}.disabled = false;".
321             "      what.form.${layer}__${field}.style.backgroundColor = '#ffffff';".
322             "      what.form.${layer}__${field}.style.display = 'none';".
323             "      if ( what.form.${layer}__${field}_classnum ) {".
324             "        what.form.${layer}__${field}_classnum.disabled = false;".
325             "        what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';".
326             "        what.form.${layer}__${field}_classnum.style.display = '';".
327             "      }".
328             '    }',
329             '  }',
330             '</SCRIPT>',
331           );
332
333         }
334
335         $html .= qq!</TD><TD CLASS="grid" BGCOLOR="$bgcolor">!;
336
337         my $disabled = $flag ? ''
338                              : 'DISABLED STYLE="background-color: #dddddd"';
339
340         if ( ! ref($def) || $def->{type} eq 'text' ) {
341
342           my $nodisplay = ' STYLE="display:none"';
343           my $is_inv = ( $flag =~ /^[MA]$/ );
344
345           $html .=
346             qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value" !.
347             $disabled.
348             ( $is_inv ? $nodisplay : $disabled ).
349             '>';
350
351           $html .= include('/elements/select-table.html',
352                              'element_name' => "${layer}__${field}_classnum",
353                              'element_etc'  => ( $is_inv
354                                                    ? $disabled
355                                                    : $nodisplay
356                                                ),
357                              'table'        => 'inventory_class',
358                              'name_col'     => 'classname',
359                              'value'        => $value,
360                              'empty_label'  => 'Select inventory class',
361                           );
362
363         } elsif ( $def->{type} eq 'select' ) {
364
365           $html .= qq!<SELECT NAME="${layer}__${field}" $disabled>!;
366           $html .= '<OPTION> </OPTION>' unless $value;
367           if ( $def->{select_table} ) {
368             foreach my $record ( qsearch( $def->{select_table}, {} ) ) {
369               my $rvalue = $record->getfield($def->{select_key});
370               $html .= qq!<OPTION VALUE="$rvalue"!.
371                        ( $rvalue==$value ? ' SELECTED>' : '>' ).
372                        $record->getfield($def->{select_label}). '</OPTION>';
373             } #next $record
374           } else { # select_list
375             foreach my $item ( @{$def->{select_list}} ) {
376               $html .= qq!<OPTION VALUE="$item"!.
377                        ( $item eq $value ? ' SELECTED>' : '>' ).
378                        $item. '</OPTION>';
379             } #next $item
380           } #endif
381           $html .= '</SELECT>';
382
383         } elsif ( $def->{type} eq 'radius_usergroup_selector' ) {
384
385           #XXX disable the RADIUS usergroup selector?  ugh it sure does need
386           #an overhaul, people have dum group problems because of it
387
388           $html .= FS::svc_acct::radius_usergroup_selector(
389             [ split(',', $value) ], "${layer}__${field}" );
390
391         } elsif ( $def->{type} eq 'disabled' ) {
392
393           $html .=
394             qq!<INPUT TYPE="hidden" NAME="${layer}__${field}" VALUE="">!;
395
396         } else {
397
398           $html .= '<font color="#ff0000">unknown type'. $def->{type};
399
400         }
401
402         $html .= "</TD></TR>\n";
403
404       } #foreach my $field (@fields) {
405
406       $part_svc->svcpart('') if $clone; #undone
407       $html .= "</TABLE>";
408
409       $html .= include('/elements/progress-init.html',
410                          $layer, #form name
411                          [ qw(svc svcpart disabled exportnum), @fields ],
412                          'process/part_svc.cgi',
413                          $p.'browse/part_svc.cgi',
414                          $layer,
415                       );
416       $html .= '<BR><INPUT NAME="submit" TYPE="button" VALUE="'.
417                ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '" '.
418                ' onClick="document.'. "$layer.submit.disabled=true; ".
419                "fixup(document.$layer); $layer". 'process();">';
420
421       #$html .= '<BR><INPUT TYPE="submit" VALUE="'.
422       #         ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '">';
423
424       $html;
425
426     },
427   );
428
429 %>
430 Table <%= $widget->html %>
431   </BODY>
432 </HTML>
433