From f413badbfe4676563d11b528838a21d9ceb8da14 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Wed, 6 Jul 2016 11:53:13 -0700 Subject: [PATCH] service label localization, internals and UI, #71347 --- FS/FS/Mason.pm | 1 + FS/FS/Schema.pm | 18 ++++ FS/FS/part_svc.pm | 27 +++++- FS/FS/part_svc_msgcat.pm | 131 ++++++++++++++++++++++++++ httemplate/browse/part_svc.cgi | 20 +++- httemplate/edit/elements/part_svc_column.html | 5 +- httemplate/edit/part_pkg.cgi | 75 +-------------- httemplate/edit/part_svc.cgi | 52 +++++++--- httemplate/edit/process/elements/process.html | 21 ++++- httemplate/edit/process/part_pkg.cgi | 5 +- httemplate/elements/freeside.css | 2 +- httemplate/elements/progress-init.html | 14 ++- httemplate/elements/tr-input-locale-text.html | 120 +++++++++++++++++++++++ 13 files changed, 385 insertions(+), 106 deletions(-) create mode 100644 FS/FS/part_svc_msgcat.pm create mode 100644 httemplate/elements/tr-input-locale-text.html diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 24ddf7920..d625fbd53 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -412,6 +412,7 @@ if ( -e $addl_handler_use_file ) { use FS::fiber_olt; use FS::olt_site; use FS::access_user_page_pref; + use FS::part_svc_msgcat; # Sammath Naur if ( $FS::Mason::addl_handler_use ) { diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index a50b551da..f88fea9ee 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -3682,6 +3682,24 @@ sub tables_hashref { ], }, + 'part_svc_msgcat' => { + 'columns' => [ + 'svcpartmsgnum', 'serial', '', '', '', '', + 'svcpart', 'int', '', '', '', '', + 'locale', 'varchar', '', 16, '', '', + 'svc', 'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'svcpartmsgnum', + 'unique' => [ [ 'svcpart', 'locale' ] ], + 'index' => [], + 'foreign_keys' => [ + { columns => [ 'svcpart' ], + table => 'part_svc', + }, + ], + }, + + #(this should be renamed to part_pop) 'svc_acct_pop' => { 'columns' => [ diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index 621a55410..dcc78435b 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -1,5 +1,5 @@ package FS::part_svc; -use base qw(FS::Record); +use base qw(FS::o2m_Common FS::Record); use strict; use vars qw( $DEBUG ); @@ -11,6 +11,7 @@ use FS::part_export; use FS::export_svc; use FS::cust_svc; use FS::part_svc_class; +use FS::part_svc_msgcat; FS::UID->install_callback(sub { # preload the cache and make sure all modules load @@ -621,6 +622,24 @@ sub svc_x { map { $_->svc_x } $self->cust_svc; } +=item svc_locale LOCALE + +Returns a customer-viewable service definition label in the chosen LOCALE. +If there is no entry for that locale or if LOCALE is empty, returns +part_svc.svc. + +=cut + +sub svc_locale { + my( $self, $locale ) = @_; + return $self->svc unless $locale; + my $part_svc_msgcat = qsearchs('part_svc_msgcat', { + svcpart => $self->svcpart, + locale => $locale + }) or return $self->svc; + $part_svc_msgcat->svc; +} + =back =head1 CLASS METHODS @@ -883,6 +902,12 @@ sub process { $param->{'svcpart'} = $new->getfield('svcpart'); } + $error ||= $new->process_o2m( + 'table' => 'part_svc_msgcat', + 'params' => $param, + 'fields' => [ 'locale', 'svc' ], + ); + die "$error\n" if $error; } diff --git a/FS/FS/part_svc_msgcat.pm b/FS/FS/part_svc_msgcat.pm new file mode 100644 index 000000000..6d69198ec --- /dev/null +++ b/FS/FS/part_svc_msgcat.pm @@ -0,0 +1,131 @@ +package FS::part_svc_msgcat; +use base qw( FS::Record ); + +use strict; +use FS::Locales; + +=head1 NAME + +FS::part_svc_msgcat - Object methods for part_svc_msgcat records + +=head1 SYNOPSIS + + use FS::part_svc_msgcat; + + $record = new FS::part_svc_msgcat \%hash; + $record = new FS::part_svc_msgcat { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::part_svc_msgcat object represents localized labels of a service +definition. FS::part_svc_msgcat inherits from FS::Record. The following +fields are currently supported: + +=over 4 + +=item svcpartmsgnum + +primary key + +=item svcpart + +Service definition + +=item locale + +locale + +=item svc + +Localized service name (customer-viewable) + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new record. To add the record to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'part_svc_msgcat'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid record. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('svcpartmsgnum') + || $self->ut_foreign_key('svcpart', 'part_svc', 'svcpart') + || $self->ut_enum('locale', [ FS::Locales->locales ] ) + || $self->ut_text('svc') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L, L, schema.html from the base documentation. + +=cut + +1; + diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index dee439466..b9474636d 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -112,8 +112,24 @@ function part_export_areyousure(href) { % } - CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <% $part_svc->svc %> + CLASS="grid" BGCOLOR="<% $bgcolor %>"> + + <% $part_svc->svc %> + +% # any alternate names of the service +% my %msgcat = map { $_->locale => $_ } $part_svc->part_svc_msgcat; +% my %labels = map { $_ => FS::Locales->description($_) } keys %msgcat; +% my @locales = sort { $labels{$a} cmp $labels{$b} } keys %msgcat; +% if ( @locales ) { +
+ +% foreach my $locale (@locales) { + <% $labels{$locale} %>: <% $msgcat{$locale}->get('svc') %> +
+% } +
+% } + CLASS="grid" BGCOLOR="<% $bgcolor %>"> <% $svcdb %> diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html index 4e112c078..816f3428b 100644 --- a/httemplate/edit/elements/part_svc_column.html +++ b/httemplate/edit/elements/part_svc_column.html @@ -267,8 +267,9 @@ my %communigate_fields = ( <& /elements/progress-init.html, $svcdb, #form name [ # form fields to send - qw(svc svcpart classnum selfservice_access disabled preserve exportnum), - @fields + 'ALL' +# qw(svc svcpart classnum selfservice_access disabled preserve exportnum), +# @fields ], 'process/part_svc.cgi', # target $p.'browse/part_svc.cgi', # redirect landing diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index 80a61f813..f2c4aacef 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -92,7 +92,7 @@ { type => 'columnstart' }, { field => 'pkg', - type => 'text', + type => 'input-locale-text', size => 40, #32 maxlength => 50, }, @@ -495,42 +495,6 @@ my $recur_show_zero_disabled = 1; my $pkgpart = ''; -my $splice_locale_fields = sub { - my( $fields, $pkey_value_callback, $pkg_value_callback ) = @_; - - my $n = 0; - my @locale_fields = ( - map { - my $pkey_value= $pkey_value_callback ? &$pkey_value_callback($_) : ''; - my $pkg_value = $pkg_value_callback - ? $pkg_value_callback eq 'cgiparam' - ? $cgi->param('pkgpartmsgnum'. $n. '_pkg') - : &$pkg_value_callback($_) - : ''; - ( - { field => 'pkgpartmsgnum'. $n, - type => 'hidden', - value => $pkey_value, - }, - { field => 'pkgpartmsgnum'. $n. '_locale', - type => 'hidden', - value => $_, - }, - { field => 'pkgpartmsgnum'. $n++. '_pkg', - type => 'text', - size => 40, - #maxlength => 50, - value => $pkg_value, - }, - ); - - } - @locales - ); - splice(@$fields, 7, 0, @locale_fields); #XXX 7 is arbitrary above - -}; - my $error_callback = sub { my($cgi, $object, $fields, $opt ) = @_; @@ -579,16 +543,6 @@ my $error_callback = sub { $pkgpart = $object->pkgpart; - &$splice_locale_fields( - $fields, - sub { - my $locale = shift; - my $part_pkg_msgcat = $object->part_pkg_msgcat($locale); - $part_pkg_msgcat ? $part_pkg_msgcat->pkgpartmsgnum : ''; - }, - 'cgiparam' - ); - if ( $cgi->param('error') =~ / is suggested with / ) { #yeah, detection is a shitty kludge, but we don't have exception objects $opt->{form_init} = ' Override suggestion

'; @@ -665,20 +619,6 @@ my $edit_callback = sub { $pkgpart = $object->pkgpart; - &$splice_locale_fields( - $fields, - sub { - my $locale = shift; - my $part_pkg_msgcat = $object->part_pkg_msgcat($locale); - $part_pkg_msgcat ? $part_pkg_msgcat->pkgpartmsgnum : ''; - }, - sub { - my $locale = shift; - my $part_pkg_msgcat = $object->part_pkg_msgcat($locale); - $part_pkg_msgcat ? $part_pkg_msgcat->pkg : ''; - } - ); - }; my $new_callback = sub { @@ -692,8 +632,6 @@ my $new_callback = sub { $options{'suspend_bill'}=1 if $conf->exists('part_pkg-default_suspend_bill'); - &$splice_locale_fields($fields, '', ''); - }; my $clone_callback = sub { @@ -732,17 +670,6 @@ my $clone_callback = sub { foreach keys %part_pkg_currency; } - $recur_disabled = $object->freq ? 0 : 1; - - &$splice_locale_fields( - $fields, - '', - sub { - my $locale = shift; - my $part_pkg_msgcat = $object->part_pkg_msgcat($locale); - $part_pkg_msgcat ? $part_pkg_msgcat->pkg : ''; - } - ); }; my $discount_error_callback = sub { diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index a07fc6005..fed21256f 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -36,9 +36,26 @@ } -
+ Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %> - - - - +<& /elements/tr-input-locale-text.html, + 'object' => $part_svc, + 'cgi' => $cgi, + 'field' => 'svc', + 'label' => 'Service', + 'curr_value' => $hashref->{svc}, +&> +%# +%# +%# +%# <& /elements/tr-select-part_svc_class.html, curr_value=>$hashref->{classnum} &> - + - + - + @@ -240,12 +264,12 @@ my $widget = new HTML::Widgets::SelectLayers( #'selected_layer' => $p_svcdb, 'selected_layer' => $hashref->{svcdb} || 'svc_acct', 'options' => \%svcdb, - 'form_name' => 'dummy', + 'form_name' => 'SvcEditMain', #'form_action' => 'process/part_svc.cgi', 'form_action' => 'part_svc.cgi', #self - 'form_elements' => [qw( svc svcpart classnum selfservice_access - disabled preserve - )], +# 'form_elements' => [qw( svc svcpart classnum selfservice_access +# disabled preserve +# )], 'html_between' => $help, 'layer_callback' => sub { include('elements/part_svc_column.html', diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index fd12c61d9..60aaf749a 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -62,6 +62,8 @@ Example: 'fields' => [qw( fieldname fieldname2 )], }, + 'process_locale' => 'fieldname', # update entries in the _msgcat table + 'process_upload' => { 'process' => 'misc/mytable-import.html', # fields to pass to the back end job, besides the @@ -363,12 +365,21 @@ foreach my $value ( @values ) { } - if ( !$error && $opt{'process_o2m'} ) { - - my @process_o2m = ref($opt{'process_o2m'}) eq 'ARRAY' - ? @{ $opt{'process_o2m'} } - : ( $opt{'process_o2m'} ); + my @process_o2m; + if ( $opt{'process_o2m'} ) { + @process_o2m = ref($opt{'process_o2m'}) eq 'ARRAY' + ? @{ $opt{'process_o2m'} } + : ( $opt{'process_o2m'} ); + } + if ( $opt{'process_locale'} ) { + push @process_o2m, + { + 'table' => $table . '_msgcat', + 'fields' => [ 'locale', $opt{'process_locale'} ], + }; + } + if ( !$error ) { foreach my $process_o2m (@process_o2m) { diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index b8042026a..c4d150ba1 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -9,6 +9,7 @@ 'edit_ext' => 'cgi', 'precheck_callback' => $precheck_callback, 'args_callback' => $args_callback, + 'process_locale' => 'pkg', 'process_m2m' => \@process_m2m, 'process_o2m' => \@process_o2m, ) @@ -310,10 +311,6 @@ foreach my $amount_param ( grep /^usagepricepart(\d+)_amount$/, $cgi->param ) { my @process_o2m = ( { - 'table' => 'part_pkg_msgcat', - 'fields' => [qw( locale pkg )], - }, - { 'table' => 'part_pkg_usageprice', 'fields' => [qw( price currency action target amount )], diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css index fb5e7d961..cc104a196 100644 --- a/httemplate/elements/freeside.css +++ b/httemplate/elements/freeside.css @@ -235,7 +235,7 @@ div.fstabcontainer { .fsinnerbox th { font-weight:normal; font-size:80%; - valign: bottom; + vertical-align: bottom; color: #666666; } diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html index e38dde65f..0c2b8165a 100644 --- a/httemplate/elements/progress-init.html +++ b/httemplate/elements/progress-init.html @@ -98,14 +98,14 @@ function <%$key%>process () { overlib( 'Submitting job to server...', WIDTH, 444, HEIGHT, 168, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); + // jQuery .serializeArray() maybe? + var copy_fields = <% encode_json(\%copy_fields) %>; var Hash = new Array(); var x = 0; var fieldName; for (var i = 0; i.elements.length; i++) { field = document.<%$formname%>.elements[i]; - if ( <% join(' || ', map { "(field.name.indexOf('$_') > -1)" } @$fields ) %> - ) - { + if ( <% $all_fields %> || copy_fields[ field.name ] ) { if ( field.type == 'select-multiple' ) { //alert('select-multiple ' + field.name); for (var j=0; j < field.options.length; j++) { @@ -168,6 +168,14 @@ $progress_url->query_form( %dest_info, ); +my $all_fields = 0; +my %copy_fields; +if (grep '/^ALL$/', @$fields) { + $all_fields = 1; +} else { + %copy_fields = map { $_ => 1 } @$fields; +} + #stupid safari is caching the "location" of popup iframs, and submitting them #instead of displaying them. this should prevent that. my $popup_name = 'popup-'.random_id(); diff --git a/httemplate/elements/tr-input-locale-text.html b/httemplate/elements/tr-input-locale-text.html new file mode 100644 index 000000000..110a8aa9b --- /dev/null +++ b/httemplate/elements/tr-input-locale-text.html @@ -0,0 +1,120 @@ +<%doc> +Usage: + +In edit/foo.html: + +<& /elements/tr-input-locale-text.html, + cgi => $cgi, # needed to preserve values in error redirect + object => $record, + field => 'myfield', + label => 'My Field', +&> + +And in edit/process/foo.html: +<& elements/process.html, + ... + process_locale => 'myfield', +&> + +'object' needs to be an FS::Record subclass instance for a table that has +a '_msgcat' localization table. For a table "foo" where "foo.myfield" +contains some customer-visible label (in the default locale), +"foo_msgcat.myfield" contains the translation of that label for a customer +locale. The foreign key in foo_msgcat must have the same name as the primary +key of foo. + +Currently only a single field can be localized this way; including this +element more than once in the form will lead to conflicts. This is how +it should work; if at some point we need to localize several fields of the +same record, we should modify this element to show multiple inputs for each +locale. + + +<%init> + +my %opt = @_; +my $object = delete $opt{object}; +my $field = delete $opt{field}; + +# identify our locales +my $conf = FS::Conf->new; +my $default_locale = $conf->config('locale') || 'en_'; +my @locales = grep { ! /^$default_locale/ } $conf->config('available-locales'); + +my $label = delete $opt{label}; +my %labels = map { $_ => "$label—".FS::Locales->description($_) } + @locales; +@locales = sort { $labels{$a} cmp $labels{$b} } @locales; +my %curr_values; + +# where are the msgcat records? +my $msgcat_table = $object->table . '_msgcat'; +my $msgcat_pkey = dbdef->table($msgcat_table)->primary_key; +my %msgcat_pkeyvals; + +# find existing msgcat records, if any, and record their message values +# and pkeys +my $pkey = $object->primary_key; +my $pkeyval = $object->get($pkey); +if ($pkeyval) { # of course if this is a new record there won't be any + my @linked = qsearch($msgcat_table, { $pkey => $pkeyval }); + foreach (@linked) { + $curr_values{ $_->locale } = $_->get( $field ); + $msgcat_pkeyvals{ $_->locale } = $_->get( $msgcat_pkey ); + } +} + +# sticky-on-error the locale inputs +if( my $cgi = $opt{cgi} ) { + my $i = 0; + # they're named 'foomsgnum0_locale' and 'foomsgnum0_myfield' + while ( my $locale = $cgi->param($msgcat_pkey . $i . '_locale') ) { + my $value = $cgi->param($msgcat_pkey . $i . '_' . $field); + $curr_values{ $locale } = $value; + $i++; + } +} + +# compat with tr-input-text for styling +my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : ''; + +my $colspan = $opt{'colspan'} ? 'COLSPAN="'.$opt{'colspan'}.'"' : ''; + + + +% # pass through %opt on all of these to retain formatting +% # one tr, td, and input for the default locale +<& tr-input-text.html, + %opt, + 'label' => $label, + 'field' => $field +&> +% # and one for each of the others +% my $i = 0; +% foreach my $locale (@locales) { +% my $basename = $msgcat_pkey . $i; +% my $lfield = $basename . '_' . $field; +<& tr-td-label.html, + %opt, + 'id' => $lfield, # uniqueness + 'label' => $labels{$locale} +&> + + +% $i++; +% } # foreach $locale -- 2.11.0
Service
Service
Self-service accessSelf-service access
Disable new ordersDisable new orders {disabled} eq 'Y' ? ' CHECKED' : '' %>>
Preserve this service on package cancellationPreserve this service on package cancellation {'preserve'} eq 'Y' ? ' CHECKED' : '' %>> 
<% $cell_style %> ID="<% $lfield %>_input0"> + <& hidden.html, + 'field' => $basename, + 'curr_value' => $msgcat_pkeyvals{$locale}, + # will be empty if this is a new record and/or new locale, that's fine + &> + <& hidden.html, + 'field' => $basename . '_locale', + 'curr_value' => $locale, + &> + <& input-text.html, + %opt, + 'field' => $lfield, + 'curr_value' => $curr_values{$locale}, + &> +