From: mark Date: Thu, 16 Feb 2012 02:13:39 +0000 (+0000) Subject: svc_hardware revision number, #16266 X-Git-Url: http://git.freeside.biz/gitweb/?a=commitdiff_plain;h=e850a39a9091bd02885c6c94715c67c1bae65b29;p=freeside.git svc_hardware revision number, #16266 --- diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 7975a302a..ea154bf86 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2129,12 +2129,13 @@ sub tables_hashref { 'hardware_type' => { 'columns' => [ - 'typenum', 'serial', '', '', '', '', - 'classnum', 'int', '', '', '', '', - 'model', 'varchar', '', $char_d, '', '', + 'typenum', 'serial', '', '', '', '', + 'classnum', 'int', '', '', '', '', + 'model', 'varchar', '', $char_d, '', '', + 'revision','varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'typenum', - 'unique' => [ ], + 'unique' => [ [ 'classnum', 'model', 'revision' ] ], 'index' => [ ], }, diff --git a/FS/FS/hardware_class.pm b/FS/FS/hardware_class.pm index 073a97f88..791653baa 100644 --- a/FS/FS/hardware_class.pm +++ b/FS/FS/hardware_class.pm @@ -112,7 +112,11 @@ Returns all L objects belonging to this class. sub hardware_type { my $self = shift; - return qsearch('hardware_type', { 'classnum' => $self->classnum }); + qsearch({ + table => 'hardware_type', + hashref => { 'classnum' => $self->classnum }, + order_by=> 'ORDER BY model, revision', + }) } =back diff --git a/FS/FS/hardware_type.pm b/FS/FS/hardware_type.pm index ba19fcb21..f19a3f683 100644 --- a/FS/FS/hardware_type.pm +++ b/FS/FS/hardware_type.pm @@ -39,6 +39,8 @@ to which this device type belongs. =item model - descriptive model name or number +=item revision - revision name/number, subordinate to model + =back =head1 METHODS @@ -102,6 +104,7 @@ sub check { $self->ut_numbern('typenum') || $self->ut_foreign_key('classnum', 'hardware_class', 'classnum') || $self->ut_text('model') + || $self->ut_textn('revision') ; return $error if $error; @@ -119,6 +122,17 @@ sub hardware_class { return qsearchs('hardware_class', { 'classnum' => $self->classnum }); } +=item description + +Returns the model and revision number. + +=cut + +sub description { + my $self = shift; + $self->model . ($self->revision ? ' '.$self->revision : ''); +} + =back =head1 SEE ALSO diff --git a/httemplate/browse/hardware_class.html b/httemplate/browse/hardware_class.html index aef0fa39e..0bf314e3f 100644 --- a/httemplate/browse/hardware_class.html +++ b/httemplate/browse/hardware_class.html @@ -33,7 +33,17 @@ my $types_sub = sub { my $hardware_class = shift; my @rows = map { my $type_link = $p.'edit/hardware_type.html?'.$_->typenum; - [ { 'data' => $_->model, 'link' => $type_link }, ] + my $num_svcs = FS::svc_hardware->count("typenum = ".$_->typenum); + $num_svcs = $num_svcs > 0 ? + mt('[_1] [numerate,_1,service]',$num_svcs) : ''; + my $search_link = $p.'search/svc_hardware.cgi?typenum='.$_->typenum; + + [ + { 'data' => $_->model, 'link' => $type_link }, + { 'data' => $_->revision, 'link' => $type_link }, + { 'data' => $num_svcs, 'link' => $search_link, 'size' => -1 } + ] + } $hardware_class->hardware_type; \@rows; diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html index 0955d49c6..38716f06e 100644 --- a/httemplate/edit/elements/svc_Common.html +++ b/httemplate/edit/elements/svc_Common.html @@ -109,7 +109,6 @@ $f->{'hashref'} = { 'classnum'=>$columndef->columnvalue }; - $f->{'empty_label'} = 'Select hardware type'; } if ( $f->{'type'} eq 'select-svc_pbx' diff --git a/httemplate/edit/hardware_type.html b/httemplate/edit/hardware_type.html index 09a272402..717440166 100644 --- a/httemplate/edit/hardware_type.html +++ b/httemplate/edit/hardware_type.html @@ -6,6 +6,7 @@ 'typenum' => 'Type number', 'model' => 'Device model', 'classnum' => 'Hardware class', + 'revision' => 'Revision', }, 'viewall_url' => $p.'browse/hardware_class.html', ) @@ -22,7 +23,8 @@ my @fields = ( disable_empty => 1, name_col => 'classname', }, - 'model', + { field => 'model', size => 50, }, + { field => 'revision', size => 50, }, ); diff --git a/httemplate/elements/select-hardware_type.html b/httemplate/elements/select-hardware_type.html index ae07798fc..126576d0d 100644 --- a/httemplate/elements/select-hardware_type.html +++ b/httemplate/elements/select-hardware_type.html @@ -1,14 +1,36 @@ -<% include( '/elements/select-table.html', - 'table' => 'hardware_type', - 'name_col' => 'model', - 'hashref' => $hashref, - %opt, - ) -%> +<& /elements/select-tiered.html, tiers => [ + { + field => 'classnum', + table => 'hardware_class', + hashref => ($classnum ? { classnum => $classnum } : {}), + name_col => 'classname', + empty_label => '(all)', + }, + { + field => 'model', + table => 'hardware_type', + select => 'classnum, model', + name_col => 'model', + value_col => 'model', + link_col => 'classnum', + hashref => $hashref, + extra_sql => 'GROUP BY classnum, model', + empty_label => '(all)', + }, + { + table => 'hardware_type', + name_col => 'revision', + value_col => 'typenum', + link_col => 'model', + empty_label => $opt{'empty_label'}, + }, +], + field => 'typenum', + %opt, +&> <%init> my %opt = @_; -my $classnum = delete $opt{'classnum'}; my $hashref = $opt{'hashref'} || {}; -$hashref->{'classnum'} = $classnum if $classnum; +my $classnum = $hashref->{classnum}; diff --git a/httemplate/elements/select-tiered.html b/httemplate/elements/select-tiered.html new file mode 100644 index 000000000..35f9e5a60 --- /dev/null +++ b/httemplate/elements/select-tiered.html @@ -0,0 +1,191 @@ +<%doc> +Usage: + +<& /elements/select-tiered.html, + tiers => [ + { table => 'table1', ... }, # most select-table options are supported + { table => 'table2', ..., link_col = 't2num' }, # foreign key in table1 + ], + prefix => '', # to avoid name conflicts + curr_value => 42, # in the last table + field => 'fieldname', # NAME attribute of the last element +&> + +This creates a group of SELECT elements (similar to select-table.html) for +drill-down navigation of data with one-to-many relationships. + +'tiers' is required, and must be an arrayref of hashes, each describing one +tier of selection (from most general to most specific). Each tier can +contain the following: +- table, select, addl_from, hashref, extra_sql: as in FS::Record::qsearch. +- records, an arrayref of exact records. Either this or "table" must be + provided. +- field: the NAME attribute of the select element. Optional. +- name_col: the column/method name to obtain the record's text label in the + select element. +- value_col: the column/method name to obtain the record's value, which is + sent on form submission. Defaults to the primary key. +- link_col: the column/method name to associate the record to the value_col + of a record in the previous table's value_col. (That is, the foreign key.) +- empty_label: the label to use for an option with the logical meaning of + "all of these" and a value of ''. +- curr_value: the currently selected value. This will constrain the current + values of preceding tiers. +- multiple: set to true for a multiple-style selector. This should work but + isn't fully tested. +- after: an HTML string to be inserted after the select element, before + the next one. By default there's nothing between them. + +For convenience, "curr_value" and "field" can be passed as part of the +main argument list, and will be applied to the last tier. + + +% $i = 0; +% foreach my $tier (@$tiers) { +% my $onchange; +% $onchange="onchange='${pre}select_change(this, $i)'" +% if $i < scalar(@$tiers) - 1; + +<% $tier->{after} %> +% } #foreach $tier + +<%init> +my %opt = @_; +my $pre = $opt{prefix} || ''; +my $tiers = $opt{tiers} or die "no tiers defined"; + +my $i; +for( $i = 0; $i < @$tiers; $i++ ) { + my $tier = $tiers->[$i]; + my $key = $tier->{value_col}; + my $name_col = $tier->{name_col}; + if ( !exists($tier->{records}) ) { + # minor false laziness w/ select-table + my $dbdef_table = dbdef->table($tier->{table}) + or die "can't find dbdef for ".$tier->{table}." table\n"; + $key ||= $dbdef_table->primary_key; + my $hashref = $tier->{hashref} || {}; + my $select = $tier->{select} || '*'; + # we don't yet support agent_virt + $tier->{records} = [ qsearch({ + 'select' => $select, # the real magic + 'table' => $tier->{table}, + 'addl_from' => $tier->{addl_from}, + 'hashref' => $hashref, + 'extra_sql' => $tier->{extra_sql}, + }) ]; + } + + # set up options + my %children_of; + if ( $i == 0 ) { + $children_of{''} = { + map { $_->$key => $_->$name_col } @{ $tier->{records} } + }; + } + else { + my $link_col = $tier->{link_col} + or die "no link_col in '".$tier->{table}."' tier\n"; + # %children_of maps the option values in the previous tier + # to hashes of their linked options in this tier. + foreach my $rec (@{ $tier->{records} }) { + $children_of{ $rec->$link_col } ||= {}; + $children_of{ $rec->$link_col }->{ $rec->$key } = $rec->$name_col; + } + } + + if ( defined $tier->{empty_label} ) { + foreach my $key (keys %children_of) { + # only create "all" options if there are multiple choices + if ( scalar(keys %{ $children_of{$key} }) > 1 ) { + $children_of{$key}->{''} = $tier->{empty_label}; + } + } + } + $tier->{by_key} = \%children_of; +} + +$i = scalar(@$tiers) - 1; +$tiers->[$i]->{curr_value} ||= $opt{curr_value}; +$tiers->[$i]->{field} ||= $opt{field}; + +# We expect the usual case to be $opt{curr_value}, i.e. +# current value in the last tier. So trace it backward. +while($i >= 1) { + my $curr_value = $tiers->[$i]->{curr_value}; + last if !defined($curr_value); + + my $tier = $tiers->[$i]; + foreach my $key ( %{ $tier->{by_key} } ) { + my $options = $tier->{by_key}->{$key}; + if ( exists( $options->{$curr_value} ) ) { + warn "tier $i curr_value ($curr_value) found under key $key\n"; + $tiers->[$i-1]->{curr_value} = $key; + last; + } + } + $i--; +} + +my $tiers_by_key = [ map { $_->{by_key} } @$tiers ]; +my $curr_values = [ map { $_->{curr_value} || '' } @$tiers ]; + diff --git a/httemplate/search/report_svc_hardware.html b/httemplate/search/report_svc_hardware.html index 07a6241e3..61ba4ab8a 100755 --- a/httemplate/search/report_svc_hardware.html +++ b/httemplate/search/report_svc_hardware.html @@ -7,15 +7,19 @@ Search options - - <% include('/elements/selectlayers.html', - 'field' => 'classnum', - 'label' => '', - 'options' => \@classnums, - 'labels' => \%class_labels, - 'layer_callback' => \&layer_callback, - 'html_between' => '', - ) %> + <& /elements/tr-td-label.html, label => 'Device type' &> +%# <% include('/elements/selectlayers.html', +%# 'field' => 'classnum', +%# 'label' => '', +%# 'options' => \@classnums, +%# 'labels' => \%class_labels, +%# 'layer_callback' => \&layer_callback, +%# 'html_between' => '', +%# ) %> + + <& /elements/select-hardware_type.html, + 'empty_label' => '(all)' + &> <% include('/elements/tr-input-text.html', @@ -71,6 +75,7 @@ sub layer_callback { include('/elements/select-hardware_type.html', 'field' => 'classnum'.$classnum.'typenum', 'classnum' => $classnum, + 'prefix' => $classnum, 'empty_label' => 'any', ); } diff --git a/httemplate/search/svc_hardware.cgi b/httemplate/search/svc_hardware.cgi index 2ff868ef5..7dd0058dc 100644 --- a/httemplate/search/svc_hardware.cgi +++ b/httemplate/search/svc_hardware.cgi @@ -7,6 +7,7 @@ 'header' => [ '#', 'Service', 'Device type', + '', #revision 'Serial #', 'Hardware addr.', 'IP addr.', @@ -16,21 +17,22 @@ 'fields' => [ 'svcnum', 'svc', 'model', + 'revision', 'serial', 'hw_addr', 'ip_addr', 'smartcard', \&FS::UI::Web::cust_fields, ], - 'links' => [ ($link_svc) x 7, + 'links' => [ ($link_svc) x 8, ( map { $_ ne 'Cust. Status' ? $link_cust : '' } FS::UI::Web::cust_header() ) ], - 'align' => 'rllllll' . FS::UI::Web::cust_aligns(), - 'color' => [ ('') x 7, + 'align' => 'rlllllll' . FS::UI::Web::cust_aligns(), + 'color' => [ ('') x 8, FS::UI::Web::cust_colors() ], - 'style' => [ $svc_cancel_style, ('') x 6, + 'style' => [ $svc_cancel_style, ('') x 7, FS::UI::Web::cust_styles() ], ) %> @@ -39,7 +41,6 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); - my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) LEFT JOIN part_svc USING ( svcpart ) @@ -66,9 +67,9 @@ if ( $cgi->param('hw_addr') =~ /^(\S+)$/ ) { push @extra_sql, "hw_addr LIKE '%$hw_addr%'"; } -my $ip = NetAddr::IP->new($cgi->param('ip_addr')); -if ( $ip ) { - push @extra_sql, "ip_addr = '".lc($ip->addr)."'"; +if ( $cgi->param('ip_addr') ) { + my $ip = NetAddr::IP->new($cgi->param('ip_addr')); + push @extra_sql, "ip_addr = '".lc($ip->addr)."'" if $ip; } if ( lc($cgi->param('smartcard')) =~ /^(\w+)$/ ) { @@ -81,9 +82,14 @@ if ( $cgi->param('statusnum') =~ /^(\d+)$/ ) { if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { push @extra_sql, "hardware_type.classnum = $1"; - if ( $cgi->param('classnum'.$1.'typenum') =~ /^(\d+)$/ ) { - push @extra_sql, "svc_hardware.typenum = $1"; - } +} + +if ( $cgi->param('model') =~ /^([\w\s]+)$/ ) { + push @extra_sql, "hardware_type.model = '$1'"; +} + +if ( $cgi->param('typenum') =~ /^(\d+)$/ ) { + push @extra_sql, "svc_hardware.typenum = $1"; } if ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { @@ -103,6 +109,7 @@ my $sql_query = { 'part_svc.svc', 'cust_main.custnum', 'hardware_type.model', + 'hardware_type.revision', 'cust_pkg.cancel', FS::UI::Web::cust_sql_fields(), ), @@ -111,7 +118,6 @@ my $sql_query = { 'order_by' => "ORDER BY $orderby", 'addl_from' => $addl_from, }; - my $count_query = "SELECT COUNT(*) FROM svc_hardware $addl_from $extra_sql"; my $link_svc = [ $p.'view/svc_hardware.cgi?', 'svcnum' ]; my $link_cust = [ $p.'view/cust_main.cgi?', 'custnum' ];