From c2e126583354b58ef54ffa7f580b115b8eed1dd3 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Mon, 18 Mar 2013 19:33:34 -0700 Subject: [PATCH] multiple inventory classes for service columns, #21442 --- FS/FS/part_svc.pm | 8 +- FS/FS/part_svc_column.pm | 10 +- FS/FS/svc_Common.pm | 41 +- httemplate/browse/part_svc.cgi | 12 +- httemplate/edit/elements/edit.html | 1 + httemplate/edit/elements/part_svc_column.html | 303 ++++++++++++ httemplate/edit/elements/svc_Common.html | 27 +- httemplate/edit/part_svc.cgi | 555 ++++++---------------- httemplate/edit/process/elements/svc_Common.html | 5 + httemplate/elements/tr-select-inventory_item.html | 48 ++ httemplate/search/elements/svc_Common.html | 48 ++ httemplate/search/svc_acct.cgi | 2 +- httemplate/search/svc_broadband.cgi | 2 +- httemplate/search/svc_dish.cgi | 2 +- httemplate/search/svc_external.cgi | 2 +- httemplate/search/svc_hardware.cgi | 2 +- httemplate/search/svc_phone.cgi | 2 +- httemplate/search/svc_www.cgi | 2 +- httemplate/view/elements/svc_Common.html | 9 + httemplate/view/svc_hardware.cgi | 3 + 20 files changed, 617 insertions(+), 467 deletions(-) create mode 100644 httemplate/edit/elements/part_svc_column.html create mode 100644 httemplate/elements/tr-select-inventory_item.html create mode 100644 httemplate/search/elements/svc_Common.html diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index 6ca48894a..da794dd4c 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -757,11 +757,9 @@ sub process { if ( $flag =~ /^[MAH]$/ ) { $param->{ $f } = delete( $param->{ $f.'_classnum' } ); } - if ( $flag =~ /^S$/ - or $_ eq 'usergroup' ) { - $param->{ $f } = ref($param->{ $f }) - ? join(',', @{$param->{ $f }} ) - : $param->{ $f }; + if ( ( $flag =~ /^[MAHS]$/ or $_ eq 'usergroup' ) + and ref($param->{ $f }) ) { + $param->{ $f } = join(',', @{ $param->{ $f } }); } ( $f, $f.'_flag', $f.'_label' ); } diff --git a/FS/FS/part_svc_column.pm b/FS/FS/part_svc_column.pm index d467516ed..38ce1fa80 100644 --- a/FS/FS/part_svc_column.pm +++ b/FS/FS/part_svc_column.pm @@ -99,8 +99,14 @@ sub check { $self->columnflag(uc($1)); if ( $self->columnflag =~ /^[MA]$/ ) { - $error = - $self->ut_foreign_key( 'columnvalue', 'inventory_class', 'classnum' ); + # split, check all values independently, and normalize + my @classnums = split(/\s*,\s*/, $self->columnvalue); + foreach (@classnums) { + $self->set('columnvalue', $_); + $error = $self->ut_foreign_key( 'columnvalue', 'inventory_class', 'classnum' ); + return $error if $error; + } + $self->set('columnvalue', join(',', @classnums)); } if ( $self->columnflag eq 'H' ) { $error = diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index ff465bf17..0aea4559b 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -842,13 +842,20 @@ sub set_auto_inventory { next if $columnflag eq 'A' && $self->$field() ne ''; my $classnum = $part_svc_column->columnvalue; - my %hash = ( 'classnum' => $classnum ); + my %hash; if ( $columnflag eq 'A' && $self->$field() eq '' ) { $hash{'svcnum'} = ''; } elsif ( $columnflag eq 'M' ) { return "Select inventory item for $field" unless $self->getfield($field); $hash{'item'} = $self->getfield($field); + my $chosen_classnum = $self->getfield($field.'_classnum'); + if ( grep {$_ == $chosen_classnum} split(',', $classnum) ) { + $classnum = $chosen_classnum; + } + # otherwise the chosen classnum is either (all), or somehow not on + # the list, so ignore it and choose the first item that's in any + # class on the list } my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql( @@ -859,18 +866,30 @@ sub set_auto_inventory { my $inventory_item = qsearchs({ 'table' => 'inventory_item', 'hashref' => \%hash, - 'extra_sql' => "AND $agentnums_sql", + 'extra_sql' => "AND classnum IN ($classnum) AND $agentnums_sql", 'order_by' => 'ORDER BY ( agentnum IS NULL ) '. #agent inventory first ' LIMIT 1 FOR UPDATE', }); unless ( $inventory_item ) { + # should really only be shown if columnflag eq 'A'... $dbh->rollback if $oldAutoCommit; - my $inventory_class = - qsearchs('inventory_class', { 'classnum' => $classnum } ); - return "Can't find inventory_class.classnum $classnum" - unless $inventory_class; - return "Out of ". PL_N($inventory_class->classname); + my $message = 'Out of '; + my @classnums = split(',', $classnum); + foreach ( @classnums ) { + my $class = FS::inventory_class->by_key($_) + or return "Can't find inventory_class.classnum $_"; + $message .= PL_N($class->classname); + if ( scalar(@classnums) > 2 ) { # english is hard + if ( $_ != $classnums[-1] ) { + $message .= ', '; + } + } + if ( scalar(@classnums) > 1 and $_ == $classnums[-2] ) { + $message .= 'and '; + } + } + return $message; } next if $columnflag eq 'M' && $inventory_item->svcnum == $self->svcnum; @@ -878,13 +897,14 @@ sub set_auto_inventory { $self->setfield( $field, $inventory_item->item ); #if $columnflag eq 'A' && $self->$field() eq ''; + # release the old inventory item, if there was one if ( $old && $old->$field() && $old->$field() ne $self->$field() ) { my $old_inv = qsearchs({ 'table' => 'inventory_item', - 'hashref' => { 'classnum' => $classnum, + 'hashref' => { 'svcnum' => $old->svcnum, }, - 'extra_sql' => ' AND '. + 'extra_sql' => "AND classnum IN ($classnum) AND ". '( ( svc_field IS NOT NULL AND svc_field = '.$dbh->quote($field).' )'. ' OR ( svc_field IS NULL AND item = '. dbh->quote($old->$field).' )'. ')', @@ -920,6 +940,9 @@ sub set_auto_inventory { =item return_inventory +Release all inventory items attached to this service's fields. Call +when unprovisioning the service. + =cut sub return_inventory { diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index f941ae586..0d3685355 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -175,12 +175,14 @@ function part_export_areyousure(href) { % my $value = &$formatter($part_svc->part_svc_column($field)->columnvalue); % if ( $flag =~ /^[MAH]$/ ) { % my $select_table = ($flag eq 'H') ? 'hardware_class' : 'inventory_class'; -% $select_class{$value} ||= -% qsearchs($select_table, { 'classnum' => $value } ); +% foreach my $classnum ( split(',', $value) ) { +% $select_class{$classnum} = +% qsearchs($select_table, { 'classnum' => $classnum } ); % - <% $select_class{$value} - ? $select_class{$value}->classname - : "WARNING: $select_table.classnum $value not found" %> + <% $select_class{$classnum} + ? $select_class{$classnum}->classname + : "WARNING: $select_table.classnum $classnum not found" %>
+% } % } else { <% $value %> diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index a24f23805..3e6bd5bec 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -329,6 +329,7 @@ Example: % qw( country ), #select-country % qw( width height ), #htmlarea % qw( alt_format ), #select-cust_location +% qw( classnum ), # select-inventory_item % ; % % #select-table diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html new file mode 100644 index 000000000..d03c49d2f --- /dev/null +++ b/httemplate/edit/elements/part_svc_column.html @@ -0,0 +1,303 @@ +<%doc> +To be called from part_svc.cgi. +<& elements/part_svc_column.html, + 'svc_acct', + # options... + 'part_svc' => $part_svc, # the existing part_svc to edit + 'clone' => 0, # or a svcpart to clone from +&> + + +<%once> +# the semantics of this could be better + +# all of these conditions are when NOT to allow that flag choice +# don't allow the 'inventory' flags (M, A) to be chosen for +# fields that aren't free-text +my $inv_sub = sub { $_[0]->{disable_inventory} || $_[0]->{type} ne 'text' }; +tie my %flag, 'Tie::IxHash', + '' => { 'desc' => 'No default', 'condition' => sub { 0 } }, + 'D' => { 'desc' => 'Default', + 'condition' => + sub { $_[0]->{disable_default } } + }, + 'F' => { 'desc' => 'Fixed (unchangeable)', + 'condition' => + sub { $_[0]->{disable_fixed} }, + }, + 'S' => { 'desc' => 'Selectable Choice', + 'condition' => + sub { $_[0]->{disable_select} }, + }, + 'M' => { 'desc' => 'Manual selection from inventory', + 'condition' => $inv_sub, + }, + 'A' => { 'desc' => 'Automatically fill in from inventory', + 'condition' => $inv_sub, + }, + 'H' => { 'desc' => 'Select from hardware class', + 'condition' => sub { $_[0]->{type} ne 'select-hardware' }, + }, + 'X' => { 'desc' => 'Excluded', + 'condition' => sub { 1 }, # obsolete + }, +; + +# the semantics of this could be much better +sub flag_condition { + my $f = shift; + not &{ $flag{$f}->{'condition'} }(@_); +} + +my %communigate_fields = ( + 'svc_acct' => { map { $_=>1 } + qw( file_quota file_maxnum file_maxsize + password_selfchange password_recover + ), + grep /^cgp_/, fields('svc_acct') + }, + 'svc_domain' => { map { $_=>1 } + qw( max_accounts trailer parent_svcnum ), + grep /^(cgp|acct_def)_/, fields('svc_domain') + }, +); + + +

+<& /elements/table.html &> + >Exports + +% # exports +% foreach my $part_export (@part_export) { + + exportnum} ? 'CHECKED' : '' %>> + <% $part_export->label_html %> + +% $count++; +% if ( $count % $columns == 0 ) { + + +% } +% } + +

+For the selected table, you can give fields default or fixed (unchangeable) +values, or select an inventory class to manually or automatically fill in +that field. +<& /elements/table-grid.html, cellpadding => 4 &> + + Field + Label + Modifier + +% $part_svc->set('svcpart' => $opt{'clone'}) if $opt{'clone'}; # for now +% my $i = 0; +% foreach my $field (@fields) { +% my $def = shift @defs; +% my $part_svc_column = $part_svc->part_svc_column($field); +% my $flag = $part_svc_column->columnflag; +% my $formatter = $def->{'format'} || sub { shift }; +% my $value = &{$formatter}($part_svc_column->columnvalue); + + + <% $def->{'label'} || $field %> + + + + + + +% # flag selection +% if ( $def->{'type'} eq 'disabled' ) { +% $flag = ''; + No default +% } else { +% my $name = $svcdb.'__'.$field.'_flag'; + +% } # if $def->{'type'} eq 'disabled' + + +% # value entry/selection +% my $name = $svcdb.'__'.$field; +% # These are all MANDATORY SELECT types. Regardless of the flag value, +% # there will never be a text input (either in svc_* or in part_svc) for +% # these fields. +% if ( $def->{'type'} eq 'checkbox' ) { + <& /elements/checkbox.html, + 'field' => $name, + 'curr_value' => $value, + 'value' => 'Y' &> +% +% } elsif ( $def->{'type'} eq 'select' ) { +% +% if ( $def->{'select_table'} ) { + <& /elements/select-table.html, + 'field' => $name, + 'id' => $name.'_select', + 'table' => $def->{'select_table'}, + 'name_col' => $def->{'select_label'}, + 'value_col' => $def->{'select_key'}, + 'order_by' => dbdef->table($def->{'select_table'})->primary_key, + 'multiple' => $def->{'multiple'}, + 'disable_empty' => 1, + 'curr_value' => $value, + &> +% } else { +% my (@options, %labels); +% if ( $def->{'select_list'} ) { +% @options = @{ $def->{'select_list'} }; +% @labels{@options} = @options; +% } elsif ( $def->{'select_hash'} ) { +% if ( ref($def->{'select_hash'}) eq 'ARRAY' ) { +% tie my %hash, 'Tie::IxHash', @{ $def->{'select_hash'} }; +% $def->{'select_hash'} = \%hash; +% } +% @options = keys( %{ $def->{'select_hash'} } ); +% %labels = %{ $def->{'select_hash'} }; +% } + <& /elements/select.html, + 'field' => $name, + 'id' => $name.'_select', + 'options' => \@options, + 'labels' => \%labels, + 'multiple' => $def->{'multiple'}, + 'curr_value' => $value, + &> +% } +% } elsif ( $def->{'type'} =~ /select-(.*?).html/ ) { + <& '/elements/'.$def->{'type'}, + 'field' => $name, + 'id' => $name.'_select', + 'multiple' => $def->{'multiple'}, + 'curr_value' => $value, + &> +% } elsif ( $def->{'type'} eq 'communigate_pro-accessmodes' ) { + <& /elements/communigate_pro-accessmodes.html, + 'element_name_prefix' => $name.'_', + 'curr_value' => $value, + &> +% } elsif ( $def->{'type'} eq 'textarea' ) { +% # special cases + +% } elsif ( $def->{'type'} eq 'disabled' ) { + +% } else { +% # the normal case: a text input, and a _select which is an inventory +% # or hardware class + +% # inventory class selection + <& /elements/select-table.html, + 'field' => $name.'_classnum', + 'id' => $name.'_select', + 'table' => 'inventory_class', + 'name_col' => 'classname', + 'curr_value' => $value, + 'empty_label' => 'Select inventory class', + 'multiple' => 1, + &> +% } + + + + +% if ( $def->{def_info} ) { + (<% $def->{def_info} %>) + + +% } +% $i = 1-$i; +% } # foreach my $field +% +% # special case: svc_acct password edit ACL +% if ( $svcdb eq 'svc_acct' ) { +% push @fields, 'restrict_edit_password'; + + + <% emt('Require "Provision" access right to edit password') %> + + + restrict_edit_password ? 'CHECKED' : '' %>> + + +% } + +<& /elements/progress-init.html, + $svcdb, #form name + [ # form fields to send + qw(svc svcpart classnum selfservice_access disabled preserve exportnum), + @fields + ], + 'process/part_svc.cgi', # target + $p.'browse/part_svc.cgi', # redirect landing + $svcdb, #key +&> +% $svcpart = '' if $opt{clone}; +
+ +<%init> +my $svcdb = shift; +my %opt = @_; +my $columns = 3; +my $count = 0; +my $communigate = 0; +my $conf = FS::Conf->new; + +my $part_svc = $opt{'part_svc'} || FS::part_svc->new; + +my @part_export; +my $export_info = FS::part_export::export_info($svcdb); +foreach (keys %{ $export_info }) { + push @part_export, qsearch('part_export', { exporttype => $_ }); +} +$communigate = scalar(grep {$_->exporttype =~ /^communigate/} @part_export); + +my $svcpart = $opt{'clone'} || $part_svc->svcpart; +my %has_export_svc; +if ( $svcpart ) { + foreach (qsearch('export_svc', { svcpart => $svcpart })) { + $has_export_svc{$_->exportnum} = 1; + } +} + +my @fields; +if ( defined( dbdef->table($svcdb) ) ) { # when is it ever not defined? + @fields = grep { + $_ ne 'svcnum' + and ( $communigate || ! $communigate_fields{$svcdb}->{$_} ) + and ( !FS::part_svc->svc_table_fields($svcdb)->{$_}->{disable_part_svc_column} + || $part_svc->part_svc_column($_)->columnflag ) + } fields($svcdb); +} +if ( $svcdb eq 'svc_acct' + or ( $svcdb eq 'svc_broadband' and $conf->exists('svc_broadband-radius') ) + ) +{ + push @fields, 'usergroup'; +} + +my @defs = map { FS::part_svc->svc_table_fields($svcdb)->{$_} } @fields; + diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html index 0d9d36c07..d46d1cb42 100644 --- a/httemplate/edit/elements/svc_Common.html +++ b/httemplate/edit/elements/svc_Common.html @@ -88,30 +88,13 @@ } elsif ( $flag eq 'A' ) { $f->{'type'} = 'hidden'; } elsif ( $flag eq 'M' ) { + $f->{'type'} = 'select-inventory_item'; $f->{'empty_label'} = 'Select inventory item'; - $f->{'type'} = 'select-table'; - $f->{'table'} = 'inventory_item'; - $f->{'name_col'} = 'item'; - $f->{'value_col'} = 'item'; - $f->{'agent_virt'} = 1; - $f->{'agent_null'} = 1; - $f->{'hashref'} = { - 'classnum'=>$columndef->columnvalue, - #'svcnum' => '', - }; - $f->{'extra_sql'} = 'AND ( svcnum IS NULL '; - $f->{'extra_sql'} .= ' OR svcnum = '. $object->svcnum - if $object->svcnum; - $f->{'extra_sql'} .= ' ) '; + $f->{'extra_sql'} = 'WHERE ( svcnum IS NULL ' . + ($object->svcnum && ' OR svcnum = '.$object->svcnum) . + ')'; + $f->{'classnum'} = $columndef->columnvalue; $f->{'disable_empty'} = $object->svcnum ? 1 : 0; - if ( $f->{'field'} eq 'mac_addr' ) { - $f->{'compare_sub'} = sub { - my($a, $b) = @_; - $a =~ s/[-: ]//g; - $b =~ s/[-: ]//g; - lc($a) eq lc($b); - }; - } } elsif ( $flag eq 'H' ) { $f->{'type'} = 'select-hardware_type'; $f->{'hashref'} = { diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index 8a84b208a..58c237efd 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -1,11 +1,111 @@ -<& /elements/header.html, "$action Service Definition", - menubar('View all service definitions' => "${p}browse/part_svc.cgi"), +<& /elements/header.html, "$action Service Definition" &> +<& /elements/menubar.html, + 'View all service definitions' => "${p}browse/part_svc.cgi" #" onLoad=\"visualize()\"" &> <& /elements/init_overlib.html &> -
+

+ + +
@@ -53,400 +153,6 @@
-% my %vfields; -% #code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm -% # and generalize the subs -% # condition sub is tested to see whether to disable display of this choice -% # params: ( $def, $layer, $field ) (see SUB below) -% my $inv_sub = sub { -% $_[0]->{disable_inventory} -% || $_[0]->{'type'} ne 'text' -% }; -% tie my %flag, 'Tie::IxHash', -% '' => { 'desc' => 'No default', }, -% 'D' => { 'desc' => 'Default', -% 'condition' => -% sub { $_[0]->{disable_default} }, -% }, -% 'F' => { 'desc' => 'Fixed (unchangeable)', -% 'condition' => -% sub { $_[0]->{disable_fixed} }, -% }, -% 'S' => { 'desc' => 'Selectable Choice', -% 'condition' => -% sub { !ref($_[0]) || $_[0]->{disable_select} }, -% }, -% 'M' => { 'desc' => 'Manual selection from inventory', -% 'condition' => $inv_sub, -% }, -% 'A' => { 'desc' => 'Automatically fill in from inventory', -% 'condition' => $inv_sub, -% }, -% 'H' => { 'desc' => 'Select from hardware class', -% 'condition' => sub { $_[0]->{type} ne 'select-hardware' }, -% }, -% 'X' => { 'desc' => 'Excluded', -% 'condition' => -% sub { ! $vfields{$_[1]}->{$_[2]} }, -% -% }, -% ; -% -% my @dbs = $hashref->{svcdb} -% ? ( $hashref->{svcdb} ) -% : FS::part_svc->svc_tables(); -% -% my $help = ''; -% unless ( $hashref->{svcpart} ) { -% $help = ' '. -% include('/elements/popup_link.html', -% 'action' => $p.'docs/part_svc-table.html', -% 'label' => 'help', -% 'actionlabel' => 'Service table help', -% 'width' => 763, -% #'height' => 400, -% ); -% } -% -% tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs; -% my $widget = new HTML::Widgets::SelectLayers( -% #'selected_layer' => $p_svcdb, -% 'selected_layer' => $hashref->{svcdb} || 'svc_acct', -% 'options' => \%svcdb, -% 'form_name' => 'dummy', -% #'form_action' => 'process/part_svc.cgi', -% 'form_action' => 'part_svc.cgi', #self -% 'form_elements' => [qw( svc svcpart classnum selfservice_access -% disabled preserve -% )], -% 'html_between' => $help, -% 'layer_callback' => sub { -% my $layer = shift; -% -% my $html = qq!!; -% -% #$html .= $svcdb_info; -% -% my $columns = 3; -% my $count = 0; -% my $communigate = 0; -% my @part_export = -% map { qsearch( 'part_export', {exporttype => $_ } ) } -% keys %{FS::part_export::export_info($layer)}; -% $html .= '

'. include('/elements/table.html') . -% "Exports"; -% foreach my $part_export ( @part_export ) { -% $communigate++ if $part_export->exporttype =~ /^communigate/; -% $html .= 'svcpart ) #null svcpart search causing error -% && qsearchs( 'export_svc', { -% exportnum => $part_export->exportnum, -% svcpart => $clone || $part_svc->svcpart }); -% $html .= '>'. $part_export->label_html. ''; -% $count++; -% $html .= '' unless $count % $columns; -% } -% $html .= '

'. $mod_info; -% -% $html .= include('/elements/table-grid.html', 'cellpadding' => 4 ). -% ''. -% 'Field'. -% 'Label'. -% 'Modifier'. -% ''; -% -% my $bgcolor1 = '#eeeeee'; -% my $bgcolor2 = '#ffffff'; -% my $bgcolor; -% -% #yucky kludge -% my @fields = (); -% if ( defined( dbdef->table($layer) ) ) { -% @fields = grep { -% $_ ne 'svcnum' -% && ( $communigate || !$communigate_fields{$layer}->{$_} ) -% && ( !FS::part_svc->svc_table_fields($layer) -% ->{$_}->{disable_part_svc_column} -% || $part_svc->part_svc_column($_)->columnflag -% ) -% } fields($layer); -% } -% push @fields, 'usergroup' -% if $layer eq 'svc_acct' -% or ( $layer eq 'svc_broadband' and -% $conf->exists('svc_broadband-radius') ); # double kludge -% # (but we do want to check the config, right?) -% $part_svc->svcpart($clone) if $clone; #haha, undone below -% -% -% foreach my $field (@fields) { -% -% #a few lines of false laziness w/browse/part_svc.cgi -% my $def = FS::part_svc->svc_table_fields($layer)->{$field}; -% my $def_info = $def->{'def_info'}; -% my $formatter = $def->{'format'} || sub { shift }; -% -% my $part_svc_column = $part_svc->part_svc_column($field); -% my $label = $part_svc_column->columnlabel || $def->{'label'}; -% my $value = &$formatter($part_svc_column->columnvalue); -% my $flag = $part_svc_column->columnflag; -% -% if ( $bgcolor eq $bgcolor1 ) { -% $bgcolor = $bgcolor2; -% } else { -% $bgcolor = $bgcolor1; -% } -% -% $html .= qq!!. -% ( $def->{'label'} || $field ). -% ""; -% -% $html .= qq!'; -% -% $flag = '' if $def->{type} eq 'disabled'; -% -% $html .= qq!!; -% -% if ( $def->{type} eq 'disabled' ) { -% -% $html .= 'No default'; -% -% } else { -% -% $html .= qq!'; -% -% $html .= join("\n", -% '', -% ); -% -% } -% -% $html .= qq!!; -% -% my $disabled = $flag ? '' -% : 'DISABLED STYLE="background-color: #dddddd"'; -% my $nodisplay = ' STYLE="display:none"'; -% -% if ( !$def->{type} || $def->{type} eq 'text' ) { -% -% my $is_inv = ( $flag =~ /^[MA]$/ ); -% -% $html .= -% qq!'; -% -% $html .= include('/elements/select-table.html', -% 'element_name' => "${layer}__${field}_classnum", -% 'id' => "${layer}__${field}_classnum", -% 'element_etc' => ( $is_inv -% ? $disabled -% : $nodisplay -% ), -% 'table' => 'inventory_class', -% 'name_col' => 'classname', -% 'value' => $value, -% 'empty_label' => 'Select inventory class', -% ); -% -% } elsif ( $def->{type} eq 'checkbox' ) { -% -% $html .= include('/elements/checkbox.html', -% 'field' => $layer.'__'.$field, -% 'curr_value' => $value, -% 'value' => 'Y', -% ); -% -% } elsif ( $def->{type} eq 'select' ) { -% -% $html .= qq!'; -% -% } elsif ( $def->{type} eq 'textarea' ) { -% -% $html .= -% qq!'; -% -% } elsif ( $def->{type} =~ /select-(.*?).html/ ) { -% -% $html .= include("/elements/".$def->{type}, -% 'curr_value' => $value, -% 'element_name' => "${layer}__${field}", -% 'element_etc' => $disabled, -% 'multiple' => ($def->{multiple} || -% $flag eq 'S'), -% # allow the table def to force 'multiple' -% ); -% -% } elsif ( $def->{type} eq 'communigate_pro-accessmodes' ) { -% -% $html .= include('/elements/communigate_pro-accessmodes.html', -% 'element_name_prefix' => "${layer}__${field}_", -% 'curr_value' => $value, -% #doesn't work#'element_etc' => $disabled, -% ); -% -% } elsif ( $def->{type} eq 'select-hardware' ) { -% -% $html .= qq!!; -% $html .= include('/elements/select-hardware_class.html', -% 'curr_value' => $value, -% 'element_name' => "${layer}__${field}_classnum", -% 'id' => "${layer}__${field}_classnum", -% 'element_etc' => $flag ne 'H' && $nodisplay, -% 'empty_label' => 'Select hardware class', -% ); -% -% } elsif ( $def->{type} eq 'disabled' ) { -% -% $html .= -% qq!!; -% -% } else { -% -% $html .= 'unknown type '. $def->{type}; -% -% } -% -% $html .= "\n"; - -% $def_info = "($def_info)" if $def_info; -% $html .= -% qq!!. -% qq! !. -% qq! $def_info!. -% qq! !. -% qq!\n!; -% -% } #foreach my $field (@fields) { -% -% if ( $layer eq 'svc_acct' ) { -% # eww, more ugly special-caseyness -% $html .= -% ''. -% emt('Require "Provision" access right to edit password'). -% ''. -% 'restrict_edit_password ? ' CHECKED' : ''). -% '>'; -% } else { -% $html .= -% ''; -% } -% -% $part_svc->svcpart('') if $clone; #undone -% $html .= ""; -% -% $html .= include('/elements/progress-init.html', -% $layer, #form name -% [ qw(svc svcpart classnum selfservice_access -% disabled preserve -% exportnum restrict_edit_password), -% @fields ], -% 'process/part_svc.cgi', -% $p.'browse/part_svc.cgi', -% $layer, -% ); -% $html .= '
'; -% -% #$html .= '
'; -% -% $html; -% -% }, -% ); -
Table <% $widget->html %> @@ -479,28 +185,43 @@ my $action = $part_svc->svcpart ? 'Edit' : 'Add'; my $hashref = $part_svc->hashref; # my $p_svcdb = $part_svc->svcdb || 'svc_acct'; -my %communigate_fields = ( - 'svc_acct' => { map { $_=>1 } - qw( file_quota file_maxnum file_maxsize - password_selfchange password_recover - ), - grep /^cgp_/, fields('svc_acct') - }, - 'svc_domain' => { map { $_=>1 } - qw( max_accounts trailer parent_svcnum ), - grep /^(cgp|acct_def)_/, fields('svc_domain') - }, - #'svc_forward' => { map { $_=>1 } qw( ) }, - #'svc_mailinglist' => { map { $_=>1 } qw( ) }, - #'svc_cert' => { map { $_=>1 } qw( ) }, -); -my $mod_info = ' -For the selected table, you can give fields default or fixed (unchangable) -values, or select an inventory class to manually or automatically fill in -that field. -'; +my @dbs = $hashref->{svcdb} + ? ( $hashref->{svcdb} ) + : FS::part_svc->svc_tables(); + +my $help = ''; +unless ( $hashref->{svcpart} ) { + $help = ' '. + include('/elements/popup_link.html', + 'action' => $p.'docs/part_svc-table.html', + 'label' => 'help', + 'actionlabel' => 'Service table help', + 'width' => 763, + #'height' => 400, + ); +} +tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs; +my $widget = new HTML::Widgets::SelectLayers( + #'selected_layer' => $p_svcdb, + 'selected_layer' => $hashref->{svcdb} || 'svc_acct', + 'options' => \%svcdb, + 'form_name' => 'dummy', + #'form_action' => 'process/part_svc.cgi', + 'form_action' => 'part_svc.cgi', #self + 'form_elements' => [qw( svc svcpart classnum selfservice_access + disabled preserve + )], + 'html_between' => $help, + 'layer_callback' => sub { + include('elements/part_svc_column.html', + shift, + 'part_svc' => $part_svc, + 'clone' => $clone + ) + } +); diff --git a/httemplate/edit/process/elements/svc_Common.html b/httemplate/edit/process/elements/svc_Common.html index 5a8afbd6c..06f4c00b1 100644 --- a/httemplate/edit/process/elements/svc_Common.html +++ b/httemplate/edit/process/elements/svc_Common.html @@ -10,5 +10,10 @@ my %opt = @_; my $table = $opt{'table'}; $opt{'fields'} ||= [ fields($table) ]; push @{ $opt{'fields'} }, qw( pkgnum svcpart ); +foreach (fields($table)) { + if ( $cgi->param($_.'_classnum') ) { + push @{ $opt{'fields'} }, $_.'_classnum'; + } +} diff --git a/httemplate/elements/tr-select-inventory_item.html b/httemplate/elements/tr-select-inventory_item.html new file mode 100644 index 000000000..669e85f27 --- /dev/null +++ b/httemplate/elements/tr-select-inventory_item.html @@ -0,0 +1,48 @@ +% if ( scalar(@classnums) == 0 ) { +<& tr-fixed.html, %opt &> +% } elsif ( scalar(@classnums) == 1 ) { +% $opt{'extra_sql'} .= ' AND '.$classnum_sql; +<& tr-select-table.html, + 'table' => 'inventory_item', + 'name_col' => 'item', + 'value_col' => 'item', + %opt +&> +% } else { +<& tr-td-label.html, %opt &> + +<& select-tiered.html, + 'prefix' => $opt{'field'}.'_', + 'tiers' => [ + { + field => $opt{'field'}.'_classnum', + table => 'inventory_class', + extra_sql => "WHERE $classnum_sql", + name_col => 'classname', + empty_label => '(all)', + }, + { + field => $opt{'field'}, + table => 'inventory_item', + name_col => 'item', + value_col => 'item', + link_col => 'classnum', + extra_sql => delete($opt{'extra_sql'}), + disable_empty => 1, + }, + ], + %opt, +&> + + +% } +<%init> +my %opt = @_; +my @classnums; +if (ref($opt{'classnum'})) { + @classnums = @{ $opt{'classnum'} }; +} else { + @classnums = split(',', $opt{'classnum'}); +} +my $classnum_sql = 'classnum IN('.join(',', @classnums).')'; + diff --git a/httemplate/search/elements/svc_Common.html b/httemplate/search/elements/svc_Common.html new file mode 100644 index 000000000..56c75bba3 --- /dev/null +++ b/httemplate/search/elements/svc_Common.html @@ -0,0 +1,48 @@ +<& search.html, %opt &> +<%doc> +Currently does nothing but insert the classnames for fields chosen from an +inventory class. + +<%init> +my %opt = @_; +my $query = $opt{query}; +my $svcdb = $query->{'table'}; + +# to avoid looking up the inventory class of every service in the database, +# keep as much of the base query as possible. +my $item_query = { %$query }; +$item_query->{'table'} = 'inventory_item'; +$item_query->{'addl_from'} = + " JOIN ( $svcdb ". $query->{'addl_from'} . + ") ON inventory_item.svcnum = $svcdb.svcnum ". + " JOIN inventory_class ON (inventory_item.classnum = inventory_class.classnum)"; +# avoid conflict with inventory_item.agentnum +$item_query->{'extra_sql'} =~ s/ agentnum/ cust_main.agentnum/g; +$item_query->{'select'} = 'inventory_item.svcnum, '. + 'inventory_item.svc_field, '. + 'inventory_class.classname'; +my @items = qsearch($item_query); +my %item_fields; +foreach my $i (@items) { + $item_fields{ $i->svc_field } ||= {}; + $item_fields{ $i->svc_field }{ $i->svcnum } = $i->classname; +} + +$opt{'sort_fields'} ||= []; +for ( my $i = 0; $i < @{ $opt{'fields'} }; $i++ ) { + my $f = $opt{'fields'}[$i]; + next if ref($f); # it's not a plain table column + $opt{'sort_fields'}[$i] ||= $f; + my $classnames = $item_fields{$f}; # hashref of svcnum -> classname + next if !$classnames; # there are no inventory items in this column + $opt{'fields'}[$i] = sub { + my $svc = $_[0]; + if ( exists($classnames->{$svc->svcnum}) ) { + return $svc->$f . '
('. $classnames->{$svc->svcnum} . ')'; + } else { + return $svc->$f; + } + }; #sub +} + + diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi index 92e1c500c..b9e5a7cc9 100755 --- a/httemplate/search/svc_acct.cgi +++ b/httemplate/search/svc_acct.cgi @@ -1,4 +1,4 @@ -<& elements/search.html, +<& elements/svc_Common.html, 'title' => emt('Account Search Results'), 'name' => emt('accounts'), 'query' => $sql_query, diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi index c22887e08..8366d214b 100755 --- a/httemplate/search/svc_broadband.cgi +++ b/httemplate/search/svc_broadband.cgi @@ -1,4 +1,4 @@ -<& elements/search.html, +<& elements/svc_Common.html, 'title' => 'Broadband Search Results', 'name' => 'broadband services', 'html_init' => $html_init, diff --git a/httemplate/search/svc_dish.cgi b/httemplate/search/svc_dish.cgi index 1578aa1e3..1f8cbc395 100755 --- a/httemplate/search/svc_dish.cgi +++ b/httemplate/search/svc_dish.cgi @@ -1,4 +1,4 @@ -<& elements/search.html, +<& elements/svc_Common.html, 'title' => 'Dish Network Search Results', 'name' => 'services', 'query' => $sql_query, diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi index 19099878a..b282939a7 100755 --- a/httemplate/search/svc_external.cgi +++ b/httemplate/search/svc_external.cgi @@ -1,4 +1,4 @@ -<& elements/search.html, +<& elements/svc_Common.html, 'title' => 'External service search results', 'name' => 'external services', 'query' => $sql_query, diff --git a/httemplate/search/svc_hardware.cgi b/httemplate/search/svc_hardware.cgi index 28aa13217..93fc2c391 100644 --- a/httemplate/search/svc_hardware.cgi +++ b/httemplate/search/svc_hardware.cgi @@ -1,4 +1,4 @@ -<& elements/search.html, +<& elements/svc_Common.html, 'title' => 'Hardware service search results', 'name' => 'installations', 'query' => $sql_query, diff --git a/httemplate/search/svc_phone.cgi b/httemplate/search/svc_phone.cgi index cc4aa60f5..f3a056475 100644 --- a/httemplate/search/svc_phone.cgi +++ b/httemplate/search/svc_phone.cgi @@ -1,4 +1,4 @@ -<& elements/search.html, +<& elements/svc_Common.html, 'title' => "Phone number search results", 'name' => 'phone numbers', 'query' => $sql_query, diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi index eefe89302..7410262e8 100755 --- a/httemplate/search/svc_www.cgi +++ b/httemplate/search/svc_www.cgi @@ -1,4 +1,4 @@ -<& elements/search.html, +<& elements/svc_Common.html, 'title' => 'Virtual Host Search Results', 'name' => 'virtual hosts', 'query' => $sql_query, diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html index a7f8f6df4..d735195fe 100644 --- a/httemplate/view/elements/svc_Common.html +++ b/httemplate/view/elements/svc_Common.html @@ -57,6 +57,7 @@ function areyousure(href) { <% ntable("#cccccc") %><% ntable("#cccccc",2) %> +% my @inventory_items = $svc_x->inventory_item; % foreach my $f ( @$fields ) { % % my($field, $type, $value); @@ -76,6 +77,14 @@ function areyousure(href) { % } % % my $columndef = $part_svc->part_svc_column($field); +% if ( $columndef->columnflag =~ /^[MA]$/ && $columndef->columnvalue =~ /,/ ) +% { +% # inventory-select field with multiple classes +% # show the class name to disambiguate +% my ($item) = grep { $_->svc_field eq $field } @inventory_items; +% my $class = qsearchs('inventory_class', { classnum => $item->classnum }); +% $value .= ' ('. $class->classname . ')' if $class; +% } % unless ($columndef->columnflag eq 'F' && !length($columndef->columnvalue)) { diff --git a/httemplate/view/svc_hardware.cgi b/httemplate/view/svc_hardware.cgi index aa3ff0091..eef1c1140 100644 --- a/httemplate/view/svc_hardware.cgi +++ b/httemplate/view/svc_hardware.cgi @@ -13,6 +13,9 @@ my %labels = map { $_ => ( ref($fields->{$_}) : $fields->{$_} ); } keys %$fields; + +$labels{'display_hw_addr'} = 'Hardware address'; + my $model = { field => 'typenum', type => 'text', value_callback => sub { $_[0]->hardware_type->description } -- 2.11.0