diff options
| -rw-r--r-- | FS/FS/Conf.pm | 86 | ||||
| -rw-r--r-- | FS/FS/L10N/en_ca.pm | 4 | ||||
| -rw-r--r-- | FS/FS/L10N/en_us.pm | 1 | ||||
| -rw-r--r-- | FS/FS/L10N/fr_ca.pm | 4 | ||||
| -rw-r--r-- | FS/FS/L10N/fr_fr.pm | 6 | ||||
| -rw-r--r-- | FS/FS/Locales.pm | 18 | ||||
| -rw-r--r-- | FS/FS/Schema.pm | 12 | ||||
| -rw-r--r-- | FS/FS/conf.pm | 2 | ||||
| -rw-r--r-- | FS/FS/cust_bill.pm | 104 | ||||
| -rw-r--r-- | FS/FS/cust_main.pm | 2 | ||||
| -rw-r--r-- | FS/FS/cust_main_Mixin.pm | 35 | ||||
| -rw-r--r-- | FS/FS/part_export/shellcommands.pm | 7 | ||||
| -rw-r--r-- | conf/invoice_html | 49 | ||||
| -rw-r--r-- | conf/invoice_latex | 31 | ||||
| -rw-r--r-- | conf/invoice_template | 4 | ||||
| -rw-r--r-- | httemplate/config/config-download.cgi | 38 | ||||
| -rw-r--r-- | httemplate/config/config-image.cgi | 9 | ||||
| -rw-r--r-- | httemplate/config/config-process.cgi | 6 | ||||
| -rw-r--r-- | httemplate/config/config-view.cgi | 75 | ||||
| -rw-r--r-- | httemplate/config/config.cgi | 17 | ||||
| -rw-r--r-- | httemplate/edit/cust_main/billing.html | 18 | ||||
| -rwxr-xr-x | httemplate/view/cust_bill-logo.cgi | 3 | ||||
| -rw-r--r-- | httemplate/view/cust_main/billing.html | 9 | 
23 files changed, 399 insertions, 141 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 79b7d8c93..7dcbf044c 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -47,16 +47,25 @@ but this may change in the future.  =over 4 -=item new +=item new [ HASHREF ]  Create a new configuration object. +HASHREF may contain options to set the configuration context.  Currently  +accepts C<locale>, and C<localeonly> to disable fallback to the null locale. +  =cut  sub new { -  my($proto) = @_; +  my($proto) = shift; +  my $opts = shift || {};    my($class) = ref($proto) || $proto; -  my($self) = { 'base_dir' => $base_dir }; +  my $self = { +    'base_dir'    => $base_dir, +    'locale'      => $opts->{locale}, +    'localeonly'  => $opts->{localeonly}, # for config-view.cgi ONLY +  }; +  warn "FS::Conf created with no locale fallback.\n" if $self->{localeonly};    bless ($self, $class);  } @@ -108,14 +117,26 @@ sub _usecompat {  sub _config {    my($self,$name,$agentnum,$agentonly)=@_;    my $hashref = { 'name' => $name }; -  $hashref->{agentnum} = $agentnum;    local $FS::Record::conf = undef;  # XXX evil hack prevents recursion -  my $cv = FS::Record::qsearchs('conf', $hashref); -  if (!$agentonly && !$cv && defined($agentnum) && $agentnum) { -    $hashref->{agentnum} = ''; -    $cv = FS::Record::qsearchs('conf', $hashref); +  my $cv; +  my @a = ( +    ($agentnum || ()), +    ($agentonly && $agentnum ? () : '') +  ); +  my @l = ( +    ($self->{locale} || ()), +    ($self->{localeonly} && $self->{locale} ? () : '') +  ); +  # try with the agentnum first, then fall back to no agentnum if allowed +  foreach my $a (@a) { +    $hashref->{agentnum} = $a; +    foreach my $l (@l) { +      $hashref->{locale} = $l; +      $cv = FS::Record::qsearchs('conf', $hashref); +      return $cv if $cv; +    }    } -  return $cv; +  return undef;  }  sub config { @@ -268,10 +289,14 @@ sub set {    warn "[FS::Conf] SET $name\n" if $DEBUG; -  my $old = FS::Record::qsearchs('conf', {name => $name, agentnum => $agentnum}); -  my $new = new FS::conf { $old ? $old->hash  -                                : ('name' => $name, 'agentnum' => $agentnum) -                         }; +  my $hashref = { +    name => $name, +    agentnum => $agentnum, +    locale => $self->{locale} +  }; + +  my $old = FS::Record::qsearchs('conf', $hashref); +  my $new = new FS::conf { $old ? $old->hash : %$hashref };    $new->value($value);    my $error; @@ -312,7 +337,7 @@ sub delete {    return $self->_usecompat('delete', @_) if use_confcompat;    my($name, $agentnum) = @_; -  if ( my $cv = FS::Record::qsearchs('conf', {name => $name, agentnum => $agentnum}) ) { +  if ( my $cv = FS::Record::qsearchs('conf', {name => $name, agentnum => $agentnum, locale => $self->{locale}}) ) {      warn "[FS::Conf] DELETE $name\n" if $DEBUG;      my $oldAutoCommit = $FS::UID::AutoCommit; @@ -1035,6 +1060,7 @@ my %payment_gateway_options = (      'description' => 'Subject: header on email invoices.  Defaults to "Invoice".  The following substitutions are available: $name, $name_short, $invoice_number, and $invoice_date.',      'type'        => 'text',      'per_agent'   => 1, +    'per_locale'  => 1,    },    { @@ -1065,6 +1091,7 @@ my %payment_gateway_options = (      'description' => 'Notes section for HTML invoices.  Defaults to the same data in invoice_latexnotes if not specified.',      'type'        => 'textarea',      'per_agent'   => 1, +    'per_locale'  => 1,    },    { @@ -1073,6 +1100,7 @@ my %payment_gateway_options = (      'description' => 'Footer for HTML invoices.  Defaults to the same data in invoice_latexfooter if not specified.',      'type'        => 'textarea',      'per_agent'   => 1, +    'per_locale'  => 1,    },    { @@ -1081,6 +1109,7 @@ my %payment_gateway_options = (      'description' => 'Summary initial page for HTML invoices.',      'type'        => 'textarea',      'per_agent'   => 1, +    'per_locale'  => 1,    },    { @@ -1088,6 +1117,7 @@ my %payment_gateway_options = (      'section'     => 'invoicing',      'description' => 'Return address for HTML invoices.  Defaults to the same data in invoice_latexreturnaddress if not specified.',      'type'        => 'textarea', +    'per_locale'  => 1,    },    { @@ -1152,6 +1182,7 @@ and customer address. Include units.',      'description' => 'Notes section for LaTeX typeset PostScript invoices.',      'type'        => 'textarea',      'per_agent'   => 1, +    'per_locale'  => 1,    },    { @@ -1160,6 +1191,7 @@ and customer address. Include units.',      'description' => 'Footer for LaTeX typeset PostScript invoices.',      'type'        => 'textarea',      'per_agent'   => 1, +    'per_locale'  => 1,    },    { @@ -1168,6 +1200,7 @@ and customer address. Include units.',      'description' => 'Summary initial page for LaTeX typeset PostScript invoices.',      'type'        => 'textarea',      'per_agent'   => 1, +    'per_locale'  => 1,    },    { @@ -1176,6 +1209,7 @@ and customer address. Include units.',      'description' => 'Remittance coupon for LaTeX typeset PostScript invoices.',      'type'        => 'textarea',      'per_agent'   => 1, +    'per_locale'  => 1,    },    { @@ -1254,6 +1288,7 @@ and customer address. Include units.',      'description' => 'Optional small footer for multi-page LaTeX typeset PostScript invoices.',      'type'        => 'textarea',      'per_agent'   => 1, +    'per_locale'  => 1,    },    { @@ -1832,7 +1867,12 @@ and customer address. Include units.',      'section'     => 'UI',      'description' => 'Default locale',      'type'        => 'select', -    'select_enum' => [ FS::Locales->locales ], +    'options_sub' => sub { +      map { $_ => FS::Locales->description($_) } FS::Locales->locales; +    }, +    'option_sub'  => sub { +      FS::Locales->description(shift) +    },    },    { @@ -3361,6 +3401,7 @@ and customer address. Include units.',      'type'        => 'image',      'per_agent'   => 1, #XXX just view/logo.cgi, which is for the global                          #old-style editor anyway...? +    'per_locale'  => 1,    },    { @@ -3369,6 +3410,7 @@ and customer address. Include units.',      'description' => 'Company logo for printed and PDF invoices, in EPS format.',      'type'        => 'image',      'per_agent'   => 1, #XXX as above, kinda +    'per_locale'  => 1,    },    { @@ -4554,6 +4596,20 @@ and customer address. Include units.',    },    { +    'key'         => 'available-locales', +    'section'     => '', +    'description' => 'Limit available locales (employee preferences, per-customer locale selection, etc.) to a particular set.', +    'type'        => 'select-sub', +    'multiple'    => 1, +    'options_sub' => sub {  +      map { $_ => FS::Locales->description($_) } +      grep { $_ ne 'en_US' }  +      FS::Locales->locales; +    }, +    'option_sub'  => sub { FS::Locales->description(shift) }, +  }, +   +  {      'key'         => 'translate-auto-insert',      'section'     => '',      'description' => 'Auto-insert untranslated strings for selected non-en_US locales with their default/en_US values. DO NOT TURN THIS ON.', diff --git a/FS/FS/L10N/en_ca.pm b/FS/FS/L10N/en_ca.pm new file mode 100644 index 000000000..1b71f7b9a --- /dev/null +++ b/FS/FS/L10N/en_ca.pm @@ -0,0 +1,4 @@ +package FS::L10N::en_ca; +use base qw(FS::L10N::en_us); + +1; diff --git a/FS/FS/L10N/en_us.pm b/FS/FS/L10N/en_us.pm index e8a592d78..6ad136be0 100644 --- a/FS/FS/L10N/en_us.pm +++ b/FS/FS/L10N/en_us.pm @@ -1,6 +1,5 @@  package FS::L10N::en_us;  use base qw(FS::L10N); -#use strict;  our %Lexicon = ( _AUTO=>1 ); diff --git a/FS/FS/L10N/fr_ca.pm b/FS/FS/L10N/fr_ca.pm new file mode 100644 index 000000000..22ede1b33 --- /dev/null +++ b/FS/FS/L10N/fr_ca.pm @@ -0,0 +1,4 @@ +package FS::L10N::fr_ca; +use base qw(FS::L10N::fr_fr); + +1; diff --git a/FS/FS/L10N/fr_fr.pm b/FS/FS/L10N/fr_fr.pm new file mode 100644 index 000000000..537241033 --- /dev/null +++ b/FS/FS/L10N/fr_fr.pm @@ -0,0 +1,6 @@ +package FS::L10N::fr_fr; +use base qw(FS::L10N::DBI); + +our %Lexicon = ( _AUTO => 1 ); + +1; diff --git a/FS/FS/Locales.pm b/FS/FS/Locales.pm index 607f2be2d..351f47875 100644 --- a/FS/FS/Locales.pm +++ b/FS/FS/Locales.pm @@ -28,8 +28,11 @@ Returns a list of the available locales.  =cut  tie our %locales, 'Tie::IxHash', -  'en_US', { name => 'English', country => 'United States', }, -  'iw_IL', { name => 'Hebrew',  country => 'Israel', rtl=>1, }, +  'en_CA', { name => 'English',     country => 'Canada', }, +  'en_US', { name => 'English',     country => 'United States', }, +  'fr_CA', { name => 'French',      country => 'Canada', }, +  'fr_FR', { name => 'French',      country => 'France', }, +  'iw_IL', { name => 'Hebrew',      country => 'Israel', rtl=>1, },  ;  sub locales { @@ -47,6 +50,17 @@ sub locale_info {    %{ $locales{$locale} };  } +=item description LOCALE + +Returns "Language (Country)" for a locale. + +=cut + +sub description { +  my($class, $locale) = @_; +  $locales{$locale}->{'name'} . ' (' . $locales{$locale}->{'country'} . ')'; +} +  =back  =head1 BUGS diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 67ce21b18..b36205c12 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -884,6 +884,7 @@ sub tables_hashref {          'accountcode_cdr', 'char', 'NULL', 1, '', '',          'billday',   'int', 'NULL', '', '', '',          'edit_subject', 'char', 'NULL', 1, '', '', +        'locale', 'varchar', 'NULL', 16, '', '',         ],        'primary_key' => 'custnum',        'unique' => [ [ 'agentnum', 'agent_custid' ] ], @@ -3265,13 +3266,14 @@ sub tables_hashref {      'conf' => {        'columns' => [ -        'confnum',  'serial',  '', '', '', '',  -        'agentnum', 'int',  'NULL', '', '', '',  -        'name',     'varchar', '', $char_d, '', '',  -        'value',    'text', 'NULL', '', '', '', +        'confnum',  'serial',     '',      '', '', '',  +        'agentnum', 'int',    'NULL',      '', '', '',  +        'locale',   'varchar','NULL',      16, '', '', +        'name',     'varchar',    '', $char_d, '', '',  +        'value',    'text',   'NULL',      '', '', '',        ],        'primary_key' => 'confnum', -      'unique' => [ [ 'agentnum', 'name' ]], +      'unique' => [ [ 'agentnum', 'locale', 'name' ] ],        'index' => [],      }, diff --git a/FS/FS/conf.pm b/FS/FS/conf.pm index 3faab1470..b467cec70 100644 --- a/FS/FS/conf.pm +++ b/FS/FS/conf.pm @@ -3,6 +3,7 @@ package FS::conf;  use strict;  use vars qw( @ISA );  use FS::Record; +use FS::Locales;  @ISA = qw(FS::Record); @@ -94,6 +95,7 @@ sub check {      || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')      || $self->ut_text('name')      || $self->ut_anything('value') +    || $self->ut_enum('locale', [ '', FS::Locales->locales ])    ;    return $error if $error; diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index d8d310a99..6a604e01c 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -1,8 +1,9 @@  package FS::cust_bill;  use strict; -use vars qw( @ISA $DEBUG $me $conf +use vars qw( @ISA $DEBUG $me                $money_char $date_format $rdate_format $date_format_long ); +             # but NOT $conf  use vars qw( $invoice_lines @buf ); #yuck  use Fcntl qw(:flock); #for spool_csv  use Cwd; @@ -41,6 +42,7 @@ use FS::bill_batch;  use FS::cust_bill_batch;  use FS::cust_bill_pay_pkg;  use FS::cust_credit_bill_pkg; +use FS::L10N;  @ISA = qw( FS::cust_main_Mixin FS::Record ); @@ -49,7 +51,7 @@ $me = '[FS::cust_bill]';  #ask FS::UID to run this stuff for us later  FS::UID->install_callback( sub {  -  $conf = new FS::Conf; +  my $conf = new FS::Conf; #global    $money_char       = $conf->config('money_char')       || '$';      $date_format      = $conf->config('date_format')      || '%x'; #/YY    $rdate_format     = $conf->config('date_format')      || '%m/%d/%Y';  #/YYYY @@ -364,6 +366,7 @@ cust_bill-default_agent_invid is set and it has a value, invnum otherwise.  sub display_invnum {    my $self = shift; +  my $conf = $self->conf;    if ( $conf->exists('cust_bill-default_agent_invid') && $self->agent_invid ){      return $self->agent_invid;    } else { @@ -807,6 +810,7 @@ If there is an error, returns the error, otherwise returns false.  sub apply_payments_and_credits {    my( $self, %options ) = @_; +  my $conf = $self->conf;    local $SIG{HUP} = 'IGNORE';    local $SIG{INT} = 'IGNORE'; @@ -955,6 +959,7 @@ sub generate_email {    my $self = shift;    my %args = @_; +  my $conf = $self->conf;    my $me = '[FS::cust_bill::generate_email]'; @@ -989,7 +994,7 @@ sub generate_email {      my $alternative = build MIME::Entity        'Type'        => 'multipart/alternative', -      'Encoding'    => '7bit', +      #'Encoding'    => '7bit',        'Disposition' => 'inline'      ; @@ -1017,8 +1022,8 @@ sub generate_email {      $alternative->attach(        'Type'        => 'text/plain', -      #'Encoding'    => 'quoted-printable', -      'Encoding'    => '7bit', +      'Encoding'    => 'quoted-printable', +      #'Encoding'    => '7bit',        'Data'        => $data,        'Disposition' => 'inline',      ); @@ -1240,6 +1245,7 @@ sub queueable_send {  sub send {    my $self = shift; +  my $conf = $self->conf;    my( $template, $invoice_from, $notice_name );    my $agentnums = ''; @@ -1329,6 +1335,7 @@ sub queueable_email {  #sub email_invoice {  sub email {    my $self = shift; +  my $conf = $self->conf;    my( $template, $invoice_from, $notice_name, $no_coupon );    if ( ref($_[0]) ) { @@ -1378,6 +1385,7 @@ sub email {  sub email_subject {    my $self = shift; +  my $conf = $self->conf;    #my $template = scalar(@_) ? shift : '';    #per-template? @@ -1409,6 +1417,7 @@ I<notice_name>, if specified, overrides "Invoice" as the name of the sent docume  sub lpr_data {    my $self = shift; +  my $conf = $self->conf;    my( $template, $notice_name );    if ( ref($_[0]) ) {      my $opt = shift; @@ -1444,6 +1453,7 @@ I<notice_name>, if specified, overrides "Invoice" as the name of the sent docume  #sub print_invoice {  sub print {    my $self = shift; +  my $conf = $self->conf;    my( $template, $notice_name );    if ( ref($_[0]) ) {      my $opt = shift; @@ -1483,6 +1493,7 @@ I<notice_name>, if specified, overrides "Invoice" as the name of the sent docume  sub fax_invoice {    my $self = shift; +  my $conf = $self->conf;    my( $template, $notice_name );    if ( ref($_[0]) ) {      my $opt = shift; @@ -1538,6 +1549,7 @@ enabled)  sub get_open_bill_batch {    my $self = shift; +  my $conf = $self->conf;    my $hashref = { status => 'O' };    $hashref->{'agentnum'} = $conf->exists('invoice_print_pdf-spoolagent')                               ? $self->cust_main->agentnum @@ -1560,6 +1572,7 @@ TEMPLATENAME is unused?  sub ftp_invoice {    my $self = shift; +  my $conf = $self->conf;    my $template = scalar(@_) ? shift : '';    $self->send_csv( @@ -1582,6 +1595,7 @@ TEMPLATENAME is unused?  sub spool_invoice {    my $self = shift; +  my $conf = $self->conf;    my $template = scalar(@_) ? shift : '';    $self->spool_csv( @@ -2091,6 +2105,7 @@ sub realtime_lec {  sub realtime_bop {    my( $self, $method ) = (shift,shift); +  my $conf = $self->conf;    my %opt = @_;    my $cust_main = $self->cust_main; @@ -2219,6 +2234,7 @@ I<notice_name>, if specified, overrides "Invoice" as the name of the sent docume  sub print_latex {    my $self = shift; +  my $conf = $self->conf;    my( $today, $template, %opt );    if ( ref($_[0]) ) {      %opt = %{ shift() }; @@ -2284,6 +2300,7 @@ sub print_latex {                             SUFFIX   => '.tex',                             UNLINK   => 0,                           ) or die "can't open temp file: $!\n"; +  binmode($fh, ':utf8'); # language support    print $fh join('', @filled_in );    close $fh; @@ -2352,6 +2369,7 @@ notice_name - overrides "Invoice" as the name of the sent document (templates fr  # yes: fixed width (dot matrix) text printing will be borked  sub print_generic {    my( $self, %params ) = @_; +  my $conf = $self->conf;    my $today = $params{today} ? $params{today} : time;    warn "$me print_generic called on $self with suffix $params{template}\n"      if $DEBUG; @@ -2635,7 +2653,11 @@ sub print_generic {      'total_pages'     => 1,    ); -   +  +  #localization +  my $lh = FS::L10N->get_handle($cust_main->locale); +  $invoice_data{'emt'} = sub { &$escape_function($self->mt(@_)) }; +    my $min_sdate = 999999999999;    my $max_edate = 0;    foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) { @@ -2770,9 +2792,9 @@ sub print_generic {        if ($format eq 'latex');    } -  $invoice_data{'po_line'} = +  $invoice_data{'po_line'} =       (  $cust_main->payby eq 'BILL' && $cust_main->payinfo ) -      ? &$escape_function("Purchase Order #". $cust_main->payinfo) +      ? &$escape_function($self->mt("Purchase Order #").$cust_main->payinfo)        : $nbsp;    my %money_chars = ( 'latex'    => '', @@ -2801,7 +2823,7 @@ sub print_generic {    warn "$me generating sections\n"      if $DEBUG > 1; -  my $previous_section = { 'description' => 'Previous Charges', +  my $previous_section = { 'description' => $self->mt('Previous Charges'),                             'subtotal'    => $other_money_char.                                              sprintf('%.2f', $pr_total),                             'summarized'  => $summarypage ? 'Y' : '', @@ -2813,7 +2835,7 @@ sub print_generic {      if $conf->exists('invoice_include_aging');    my $taxtotal = 0; -  my $tax_section = { 'description' => 'Taxes, Surcharges, and Fees', +  my $tax_section = { 'description' => $self->mt('Taxes, Surcharges, and Fees'),                        'subtotal'    => $taxtotal,   # adjusted below                        'summarized'  => $summarypage ? 'Y' : '',                      }; @@ -2825,7 +2847,8 @@ sub print_generic {    my $adjusttotal = 0; -  my $adjust_section = { 'description' => 'Credits, Payments, and Adjustments', +  my $adjust_section = { 'description' =>  +    $self->mt('Credits, Payments, and Adjustments'),                           'subtotal'    => 0,   # adjusted below                           'summarized'  => $summarypage ? 'Y' : '',                         }; @@ -2910,7 +2933,7 @@ sub print_generic {    if ( @pr_cust_bill && !$conf->exists('disable_previous_balance') ) {      push @buf, ['','-----------']; -    push @buf, [ 'Total Previous Balance', +    push @buf, [ $self->mt('Total Previous Balance'),                   $money_char. sprintf("%10.2f", $pr_total) ];      push @buf, ['',''];    } @@ -3066,7 +3089,7 @@ sub print_generic {    if ( $taxtotal ) {      my $total = {}; -    $total->{'total_item'} = 'Sub-total'; +    $total->{'total_item'} = $self->mt('Sub-total');      $total->{'total_amount'} =        $other_money_char. sprintf('%.2f', $self->charged - $taxtotal ); @@ -3083,7 +3106,8 @@ sub print_generic {    $invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal);    push @buf,['','-----------']; -  push @buf,[( $conf->exists('disable_previous_balance')  +  push @buf,[$self->mt(  +              $conf->exists('disable_previous_balance')                  ? 'Total Charges'                 : 'Total New Charges'               ), @@ -3092,7 +3116,7 @@ sub print_generic {    {      my $total = {}; -    my $item = 'Total'; +    my $item = $self->mt('Total');      $item = $conf->config('previous_balance-exclude_from_total')           || 'Total New Charges'        if $conf->exists('previous_balance-exclude_from_total'); @@ -3107,11 +3131,11 @@ sub print_generic {        &$embolden_function( $other_money_char.  sprintf( '%.2f', $amount ) );      if ( $multisection ) {        if ( $adjust_section->{'sort_weight'} ) { -        $adjust_section->{'posttotal'} = 'Balance Forward '. $other_money_char. -          sprintf("%.2f", ($self->billing_balance || 0) ); +        $adjust_section->{'posttotal'} = $self->mt('Balance Forward').' '. +          $other_money_char.  sprintf("%.2f", ($self->billing_balance || 0) );        } else { -        $adjust_section->{'pretotal'} = 'New charges total '. $other_money_char. -                                        sprintf('%.2f', $self->charged ); +        $adjust_section->{'pretotal'} = $self->mt('New charges total').' '. +          $other_money_char.  sprintf('%.2f', $self->charged );        }       }else{        push @total_items, $total; @@ -3316,25 +3340,24 @@ sub print_generic {      }      #setup subroutine for the template -    sub FS::cust_bill::_template::invoice_lines { -      my $lines = shift || scalar(@FS::cust_bill::_template::buf); +    #sub FS::cust_bill::_template::invoice_lines { # good god, no +    $invoice_data{invoice_lines} = sub { # much better +      my $lines = shift || scalar(@buf);        map {  -        scalar(@FS::cust_bill::_template::buf) -          ? shift @FS::cust_bill::_template::buf +        scalar(@buf) +          ? shift @buf            : [ '', '' ];        }        ( 1 .. $lines ); -    } +    };      my $lines;      my @collect;      while (@buf) {        push @collect, split("\n", -        $text_template->fill_in( HASH => \%invoice_data, -                                 PACKAGE => 'FS::cust_bill::_template' -                               ) +        $text_template->fill_in( HASH => \%invoice_data )        ); -      $FS::cust_bill::_template::page++; +      $invoice_data{'page'}++;      }      map "$_\n", @collect;    }else{ @@ -3546,6 +3569,7 @@ sub _translate_old_latex_format {  sub terms {    my $self = shift; +  my $conf = $self->conf;    #check for an invoice-specific override    return $self->invoice_terms if $self->invoice_terms; @@ -3574,10 +3598,11 @@ sub due_date2str {  sub balance_due_msg {    my $self = shift; -  my $msg = 'Balance Due'; +  my $msg = $self->mt('Balance Due');    return $msg unless $self->terms;    if ( $self->due_date ) { -    $msg .= ' - Please pay by '. $self->due_date2str($date_format); +    $msg .= ' - ' . $self->mt('Please pay by'). ' '. +      $self->due_date2str($date_format);    } elsif ( $self->terms ) {      $msg .= ' - '. $self->terms;    } @@ -3586,6 +3611,7 @@ sub balance_due_msg {  sub balance_due_date {    my $self = shift; +  my $conf = $self->conf;    my $duedate = '';    if (    $conf->exists('invoice_default_terms')          && $conf->config('invoice_default_terms')=~ /^\s*Net\s*(\d+)\s*$/ ) { @@ -3594,7 +3620,10 @@ sub balance_due_date {    $duedate;  } -sub credit_balance_msg { 'Credit Balance Remaining' } +sub credit_balance_msg {  +  my $self = shift; +  $self->mt('Credit Balance Remaining') +}  =item invnum_date_pretty @@ -3605,7 +3634,7 @@ Returns a string with the invoice number and date, for example:  sub invnum_date_pretty {    my $self = shift; -  'Invoice #'. $self->invnum. ' ('. $self->_date_pretty. ')'; +  $self->mt('Invoice #'). $self->invnum. ' ('. $self->_date_pretty. ')';  }  =item _date_pretty @@ -4011,6 +4040,7 @@ sub _condensed_total_line_generator {  sub _items_extra_usage_sections {    my $self = shift; +  my $conf = $self->conf;    my $escape = shift;    my $format = shift; @@ -4250,6 +4280,7 @@ sub _items_accountcode_cdr {  sub _items_svc_phone_sections {    my $self = shift; +  my $conf = $self->conf;    my $escape = shift;    my $format = shift; @@ -4497,6 +4528,7 @@ sub _items {  sub _items_previous {    my $self = shift; +  my $conf = $self->conf;    my $cust_main = $self->cust_main;    my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance    my @b = (); @@ -4505,7 +4537,7 @@ sub _items_previous {                 ? 'due '. $_->due_date2str($date_format)                 : time2str($date_format, $_->_date);      push @b, { -      'description' => 'Previous Balance, Invoice #'. $_->invnum. " ($date)", +      'description' => $self->mt('Previous Balance, Invoice #'). $_->invnum. " ($date)",        #'pkgpart'     => 'N/A',        'pkgnum'      => 'N/A',        'amount'      => sprintf("%.2f", $_->owed), @@ -4588,6 +4620,7 @@ sub _items_tax {  sub _items_cust_bill_pkg {    my $self = shift; +  my $conf = $self->conf;    my $cust_bill_pkgs = shift;    my %opt = @_; @@ -4913,7 +4946,7 @@ sub _items_credits {        #'description' => 'Credit ref\#'. $_->crednum.        #                 " (". time2str("%x",$_->cust_credit->_date) .")".        #                 $reason, -      'description' => 'Credit applied '. +      'description' => $self->mt('Credit applied').' '.                         time2str($date_format,$_->cust_credit->_date). $reason,        'amount'      => sprintf("%.2f",$_->amount),      }; @@ -4933,7 +4966,7 @@ sub _items_payments {      #something more elaborate if $_->amount ne ->cust_pay->paid ?      push @b, { -      'description' => "Payment received ". +      'description' => $self->mt('Payment received').' '.                         time2str($date_format,$_->cust_pay->_date ),        'amount'      => sprintf("%.2f", $_->amount )      }; @@ -5159,6 +5192,7 @@ Currently only supported on PostgreSQL.  =cut  sub due_date_sql { +  my $conf = new FS::Conf;  'COALESCE(    SUBSTRING(      COALESCE( diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 7832ecaf9..c1f95ea15 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -68,6 +68,7 @@ use FS::banned_pay;  use FS::cust_main_note;  use FS::cust_attachment;  use FS::contact; +use FS::Locales;  # 1 is mostly method/subroutine entry and options  # 2 traces progress of some operations @@ -1692,6 +1693,7 @@ sub check {      || $self->ut_floatn('credit_limit')      || $self->ut_numbern('billday')      || $self->ut_enum('edit_subject', [ '', 'Y' ] ) +    || $self->ut_enum('locale', [ '', FS::Locales->locales ])    ;    #barf.  need message catalogs.  i18n.  etc. diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm index e8e243f2d..d493060f0 100644 --- a/FS/FS/cust_main_Mixin.pm +++ b/FS/FS/cust_main_Mixin.pm @@ -538,6 +538,41 @@ sub process_email_search_result {  } +=item conf + +Returns a configuration handle (L<FS::Conf>) set to the customer's locale,  +if they have one.  If not, returns an FS::Conf with no locale. + +=cut + +sub conf { +  my $self = shift; +  return $self->{_conf} if (ref $self and $self->{_conf}); +  my $cust_main = $self->cust_main; +  my $conf = new FS::Conf {  +    'locale' => ($cust_main ? $cust_main->locale : '') +  }; +  $self->{_conf} = $conf if ref $self; +  return $conf; +} + +=item mt TEXT [, ARGS ] + +Localizes a text string (see L<Locale::Maketext>) for the customer's locale, +if they have one. + +=cut + +sub mt { +  my $self = shift; +  return $self->{_lh}->maketext(@_) if (ref $self and $self->{_lh}); +  my $cust_main = $self->cust_main; +  my $locale = $cust_main ? $cust_main->locale : ''; +  my $lh = FS::L10N->get_handle($locale); +  $self->{_lh} = $lh if ref $self; +  return $lh->maketext(@_); +} +  =back  =head1 BUGS diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index 50af45d7d..a8a57c663 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -193,7 +193,7 @@ old_ for replace operations):    <LI><code>$pkgnum</code>    <LI><code>$custnum</code>    <LI>All other fields in <b>svc_acct</b> are also available. -  <LI>The following fields from <b>cust_main</b> are also available (except during replace): company, address1, address2, city, state, zip, county, daytime, night, fax, otaker, agent_custid.  When used on the command line (rather than STDIN), they will be quoted for the shell already (do not add additional quotes). +  <LI>The following fields from <b>cust_main</b> are also available (except during replace): company, address1, address2, city, state, zip, county, daytime, night, fax, otaker, agent_custid, locale.  When used on the command line (rather than STDIN), they will be quoted for the shell already (do not add additional quotes).  </UL>  END  ); @@ -263,7 +263,7 @@ sub _export_command {      {        no strict 'refs';        foreach my $custf (qw( company address1 address2 city state zip country -                             daytime night fax otaker agent_custid +                             daytime night fax otaker agent_custid locale                          ))        {          ${$custf} = $cust_pkg->cust_main->$custf(); @@ -343,6 +343,7 @@ sub _export_command {    $fax = shell_quote $fax;    $otaker = shell_quote $otaker;     $agent_custid = shell_quote $agent_custid; +  $locale = shell_quote $locale;    my $command_string = eval(qq("$command"));    my @ssh_cmd_args = ( @@ -419,6 +420,7 @@ sub _export_replace {      if $error;    $new_agent_custid = $new_cust_main ? $new_cust_main->agent_custid : ''; +  $new_locale = $new_cust_main ? $new_cust_main->locale : '';    $old_pkgnum = $old_cust_pkg ? $old_cust_pkg->pkgnum : '';    $old_custnum = $old_cust_pkg ? $old_cust_pkg->custnum : '';    $new_pkgnum = $new_cust_pkg ? $new_cust_pkg->pkgnum : ''; @@ -432,6 +434,7 @@ sub _export_replace {    $new_crypt_password = shell_quote $new_crypt_password;    $new_ldap_password  = shell_quote $new_ldap_password;    $new_agent_custid = shell_quote $new_agent_custid; +  $new_locale = shell_quote $new_locale;    my $command_string = eval(qq("$command")); diff --git a/conf/invoice_html b/conf/invoice_html index 289ada1da..1d53683ad 100644 --- a/conf/invoice_html +++ b/conf/invoice_html @@ -11,6 +11,7 @@  .invoice_desc_more TD { font-weight: bold; font-size: 10pt }  .invoice_extdesc TD { font-size: 8pt }  .invoice_totaldesc TD { font-size: 10pt; empty-cells: show } +.allcaps { text-transform:uppercase }  </STYLE>  <table class="invoice" bgcolor="#ffffff" WIDTH=625 CELLSPACING=8><tr><td> @@ -23,26 +24,26 @@          <table CLASS="invoice_headerright" cellspacing=0>            <tr>              <td align="center"> -              Invoice date<BR> +              <%= emt('Invoice date') %><BR>                <B><%= $date %></B>              </td>              <td>              </td>              <td align="center"> -              Invoice #<BR> +              <%= emt('Invoice #') %><BR>                <B><%= $invnum %></B>              </td>              <td>              </td>              <td align="center"> -              Customer #<BR> +              <%= emt('Customer #') %><BR>                <B><%= $custnum %></B>              </td>            </tr>            <tr>              <th> </th> -            <th colspan=3 align="center"> -              <FONT SIZE="+3"><%= $notice_name ? substr($notice_name, 0, 1) : 'I' %></FONT><FONT SIZE="+2"><%= $notice_name ? uc(substr($notice_name, 1)) : 'NVOICE' %></FONT> +            <th colspan=3 align="center" class="allcaps"> +	      <FONT SIZE="+3"><%= substr(emt($notice_name),0,1) %></FONT><FONT SIZE="+2"><%= substr(emt($notice_name),1) %></FONT>              </th>              <th> </th>            </tr> @@ -64,7 +65,7 @@          %>        </td>        <%= $ship_enable ? ('<td align="left">'. -                          join('<BR>',grep length($_), '<b>Service Address</b>', +                          join('<BR>',grep length($_), '<b>'.emt('Service Address').'</b>',                                                         $ship_company,                                                         $ship_address1,                                                         $ship_address2, @@ -86,7 +87,7 @@  	    $OUT .= qq! <img src="cust_bill-barcode.cgi?invnum=$invnum;template=$template"><br> !;  	}      %> -        <%= $terms ? "Terms: $terms" : '' %><BR> +        <%= $terms ? emt('Terms') . ": $terms" : '' %><BR>          <%= $po_line %>        </td>      </tr> @@ -111,18 +112,13 @@          unless ($section->{'summarized'}) {            $OUT .= '</table>' if ( $notfirst || $section->{'pretotal'} && !$summary );            $OUT .= '<table><tr><td>'; -          if ($section->{'description'}) { -            $OUT .= -              '<p><b><font size="+1">'. uc(substr($section->{'description'},0,1)). -              '</font><font size="+0">'. uc(substr($section->{'description'},1)). +          my $sectionhead = $section->{'description'} || emt('Charges'); +          $OUT .= +              '<p class="allcaps"><b><font size="+1">'. substr($sectionhead,0,1). +              '</font><font size="+0">'. substr($sectionhead,1).                '</font></b>'. -              '<p>'; -          }else{ -            $OUT .= -              '<p><b><font size="+1">C</font><font size="+0">HARGES</font></b>'. -              '<p>'; -          } -          $OUT .= '</td></tr></table>'; +              '<p>'. +              '</td></tr></table>';            $OUT .=              '<table class="invoice_longtable" CELLSPACING=0 WIDTH="100%">'. @@ -133,14 +129,13 @@              $OUT .= $header;              $columncount = scalar(my @array = split /<\/th><th/i, $header);            } else { -            $OUT .=  '<th align="center">Ref</th>'. -                     '<th align="left">Description</th>'. -                        ( $unitprices  -                          ? '<th align="left">Unit Price</th>'. -                            '<th align="left">Quantity</th>' -                          : '' -                        ).  -                      '<th align="right">Amount</th>'; +            $OUT .=  '<th align="center">' . emt('Ref') . '</th>'. +                     '<th align="left">' . emt('Description') . '</th>'. +                     ( $unitprices  +                       ? '<th align="left">' . emt('Unit Price') . '</th>'. +                         '<th align="left">' . emt('Quantity') . '</th>' +                        : '' ). +                     '<th align="right">' . emt('Amount') . '</th>';            }              $OUT .= '</tr>'; @@ -207,7 +202,7 @@              } else {                $OUT .= qq(<td align="left" style="$style").                         ( $unitprices ? ' colspan=3>' : '>' ). -                      $section->{'description'}. ' Total </td>'. +                      $section->{'description'}. ' ' . emt('Total') . '</td>'.                        qq(<td align="right" style="$style">).                        $section->{'subtotal'}. '</td>';              } diff --git a/conf/invoice_latex b/conf/invoice_latex index 10f30cfe8..37f59d2ee 100644 --- a/conf/invoice_latex +++ b/conf/invoice_latex @@ -21,6 +21,8 @@  \usepackage{fancyhdr,lastpage,ifthen,array,fslongtable,afterpage,caption,multirow,bigstrut}
  \usepackage{graphicx}			% required for logo graphic
 +\usepackage[utf8]{inputenc}             % multilanguage support
 +\usepackage[T1]{fontenc}
  \addtolength{\voffset}{-0.0cm}		% top margin to top of header
  \addtolength{\hoffset}{-0.6cm}		% left margin on page
 @@ -125,10 +127,10 @@    \ifthenelse{\equal{\thepage}{1}}
    { % First page
      \begin{tabular}{ccc}
 -    Invoice date & Invoice \#& Customer \#\\
 +    [@-- join(' & ', emt('Invoice date'), emt('Invoice #'), emt('Customer #') ) --@]\\
      \vspace{0.2cm}
      \textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]} & \textbf{[@-- $custnum --@]} \\\hline
 -    \rule{0pt}{5ex} &~~ \huge{\textsc{[@-- $notice_name || 'Invoice' --@]}} & \\
 +    \rule{0pt}{5ex} &~~ \huge{\textsc{[@-- emt($notice_name) --@]}} & \\
      \vspace{-0.2cm}
       & & \\\hline
      \end{tabular}
 @@ -136,7 +138,7 @@    { % ... pages
      \small{
        \begin{tabular}{lll}
 -      Invoice date & Invoice \#& Customer\#\\
 +      [@-- join(' & ', emt('Invoice date'), emt('Invoice #'), emt('Customer #') ) --@]\\
        \textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]} & \textbf{[@-- $custnum --@]}\\
        \end{tabular}
      }
 @@ -161,19 +163,18 @@  \newcommand{\FSdescriptionlength} { [@-- $unitprices ? '8.2cm' : '12.8cm' --@] }
  \newcommand{\FSdescriptioncolumncount} { [@-- $unitprices ? '4' : '6' --@] }
 -\newcommand{\FSunitcolumns}{ [@-- $unitprices ? '\makebox[2.5cm][l]{\textbf{~~Unit Price}}&\makebox[1.4cm]{\textbf{~Quantity}}&' : '' --@] }
 +\newcommand{\FSunitcolumns}{ [@-- 
 +  $unitprices 
 +  ? '\makebox[2.5cm][l]{\textbf{~~'.emt('Unit Price').'}}&\makebox[1.4cm]{\textbf{~'.emt('Quantity').'}}&' 
 +  : '' --@] }
  \newcommand{\FShead}{
    \hline
    \rule{0pt}{2.5ex}
    \makebox[1.4cm]{\textbf{Ref}} &
 -%  \makebox[2.9cm][l]{\textbf{Description}}&
 -%  \makebox[1.4cm][l]{}&
 -%  \makebox[1.4cm][l]{}&
 -%  \makebox[2.5cm][l]{}&
 -  \multicolumn{\FSdescriptioncolumncount}{l}{\makebox[\FSdescriptionlength][l]{\textbf{Description}}}&
 +  \multicolumn{\FSdescriptioncolumncount}{l}{\makebox[\FSdescriptionlength][l]{\textbf{[@-- emt('Description') --@]}}}&
    \FSunitcolumns
 -  \makebox[1.6cm][r]{\textbf{Amount}} \\
 +  \makebox[1.6cm][r]{\textbf{[@-- emt('Amount') --@]}} \\
    \hline
  }
 @@ -217,7 +218,7 @@  \begin{minipage}[t]{6.4cm}
  [@--
    if ($ship_enable) {
 -    $OUT .= '\textbf{Service Address}\\\\';
 +    $OUT .= '\textbf{' . emt('Service Address') . '}\\\\';
      $OUT .= "\\addressline{$ship_company}";
      $OUT .= "\\addressline{$ship_address1}";
      $OUT .= "\\addressline{$ship_address2}";
 @@ -229,7 +230,7 @@    }
  --@]
  \begin{flushright}
 -[@-- $terms ? "Terms: $terms" : '' --@]\\
 +[@-- $terms ? emt('Terms') .": $terms" : '' --@]\\
  [@-- $po_line --@]\\
  \end{flushright}
  \end{minipage}}
 @@ -252,7 +253,7 @@          if $coupon;
        $OUT .= '\begin{longtable}{cllllllr}';
        $OUT .= '\caption*{ ';
 -      $OUT .= ($section->{'description'}) ? $section->{'description'}: 'Charges';
 +      $OUT .= ($section->{'description'}) ? $section->{'description'}: emt('Charges');
        $OUT .= '}\\\\';
        if ($section->{header_generator}) {
          $OUT .= &{$section->{header_generator}}();
 @@ -260,14 +261,14 @@          $OUT .= '\FShead';
        }
        $OUT .= '\endfirsthead';
 -      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\\\';
 +      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued from previous page').'}\\\\';
        if ($section->{header_generator}) {
          $OUT .= &{$section->{header_generator}}();
        } else {
          $OUT .= '\FShead';
        }
        $OUT .= '\endhead';
 -      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}Continued on next page...}\\\\';
 +      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued on next page...').'}\\\\';
        $OUT .= '\endfoot';
        $OUT .= '\hline';
 diff --git a/conf/invoice_template b/conf/invoice_template index ebf8ef7d0..769f043d7 100644 --- a/conf/invoice_template +++ b/conf/invoice_template @@ -1,6 +1,6 @@ -                                 { $notice_name || 'Invoice'; } -                                 { substr("Page $page of $total_pages          ", 0, 19); } { use Date::Format; time2str("%x", $date); }  Invoice #{ $invnum; } +                                 { emt($notice_name) } +                                 { substr(emt("Page [_1] of [_2]          ", $page, $total_pages), 0, 19); } { use Date::Format; time2str("%x", $date); }  { emt("Invoice #") . $invnum; }  { $company_name; } diff --git a/httemplate/config/config-download.cgi b/httemplate/config/config-download.cgi index 6979246db..c071f2a6d 100644 --- a/httemplate/config/config-download.cgi +++ b/httemplate/config/config-download.cgi @@ -1,21 +1,3 @@ -% -% -%my $conf=new FS::Conf; -% -%http_header('Content-Type' => 'application/x-unknown' ); -% -%die "No configuration variable specified (bad URL)!" # umm -%  unless $cgi->param('key'); -%$cgi->param('key') =~  /^([-\w.]+)$/; -%my $name = $1; -% -%my $agentnum; -%if ($cgi->param('agentnum') =~ /^(\d+)$/) { -%  $agentnum = $1; -%} -% -%http_header('Content-Disposition' => "attachment; filename=$name" ); -% print $conf->config_binary($name, $agentnum);  <%init>  die "access denied"    unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); @@ -25,4 +7,24 @@ if ($cgi->param('agentnum') =~ /^(\d+)$/) {    $agentnum = $1;  } +http_header('Content-Type' => 'application/x-unknown' ); + +die "No configuration variable specified (bad URL)!" # umm +  unless $cgi->param('key'); +$cgi->param('key') =~  /^([-\w.]+)$/; +my $name = $1; + +my $agentnum; +if ($cgi->param('agentnum') =~ /^(\d+)$/) { +  $agentnum = $1; +} + +my $locale = ''; +if ($cgi->param('locale') =~ /^(\w+)$/) { +  $locale = $1; +} +my $conf=new FS::Conf { 'locale' => $locale }; + +http_header('Content-Disposition' => "attachment; filename=$name" ); +print $conf->config_binary($name, $agentnum);  </%init> diff --git a/httemplate/config/config-image.cgi b/httemplate/config/config-image.cgi index 0de9d4278..0e04ab5bc 100644 --- a/httemplate/config/config-image.cgi +++ b/httemplate/config/config-image.cgi @@ -4,8 +4,6 @@  die "access denied"    unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); -my $conf = new FS::Conf; -  http_header( 'Content-Type' => 'image/png' ); #just png for now  $cgi->param('key') =~ /^([-\w.]+)$/ or die "illegal config option"; @@ -16,6 +14,13 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {    $agentnum = $1;  } +my $locale = ''; +if ( $cgi->param('locale') =~ /^(\w+)$/ ) { +  $locale = $1; +} + +my $conf = new FS::Conf { 'locale' => $locale }; +  my $logo = $conf->config_binary($name, $agentnum);  $logo = eps2png($logo) if $name =~ /\.eps$/i; diff --git a/httemplate/config/config-process.cgi b/httemplate/config/config-process.cgi index 4a84c0dd1..c62a8c784 100644 --- a/httemplate/config/config-process.cgi +++ b/httemplate/config/config-process.cgi @@ -25,7 +25,7 @@  %  }  %  %} else { -<% header('Configuration set') %> +<& /elements/header.html, 'Configuration set' &>    <SCRIPT TYPE="text/javascript">  %   my $n = 0;  %   foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { @@ -103,7 +103,9 @@ my %namecol = (  my $curuser = $FS::CurrentUser::CurrentUser;  die "access denied\n" unless $curuser->access_right('Configuration'); -my $conf = new FS::Conf; +my $locale = $cgi->param('locale') || ''; + +my $conf = new FS::Conf { 'locale' => $locale };  if ( $conf->exists('disable_settings_changes') ) {    my @changers = split(/\s*,\s*/, $conf->config('disable_settings_changes')); diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi index e7cadbe99..59646896e 100644 --- a/httemplate/config/config-view.cgi +++ b/httemplate/config/config-view.cgi @@ -16,6 +16,38 @@ Click on a configuration value to change it.  %   }  %  % } +% if ( @locales ) { +(  +% if ( $locale ) { +%   $cgi->delete('locale'); +    <a href="<%$cgi->self_url%>">global settings</a> |  +% } +<script type='text/javascript'> +function changeLocale(what) { +  //var what = document.getElementById('select-locale'); +  if(what.selectedIndex > 0) { +    what.form.submit(); +  } +} +</script> +invoice language options:  +<form action="<% $cgi->self_url %>" method="GET" style="display:inline;"> +<& /elements/select.html, +    'field' => 'locale', +    'options' => [ '', @locales ], +    'labels'  => { map {  +        my %info = FS::Locales->locale_info($_); +        $_ => "$info{name} ($info{country})" +    } @locales }, +    'curr_value' => $locale, +    'id' => 'select-locale', +    'onchange' => 'changeLocale' +    &> +  ) +%   $cgi->param('locale', $locale); +% } +</form> +  <BR><BR>  <% include('/elements/init_overlib.html') %> @@ -89,11 +121,12 @@ Click on a configuration value to change it.  %       if $agent && $cgi->param('showagent');  %  %     #indentation :/ +%     my $action = 'config.cgi?key=' . $i->key .  +%       ";agentnum=$agentnum" . ($locale ? ";locale=$locale" : '');      <tr>        <td><% include('/elements/popup_link.html', -                       'action'      => 'config.cgi?key='.      $i->key. -                                                  ';agentnum='. $agentnum, +                       'action'      => $action,                         'width'       => $width,                         'height'      => $height,                         'actionlabel' => 'Enter configuration value', @@ -128,12 +161,12 @@ Click on a configuration value to change it.              </tr>  %   } elsif ( $type eq 'image' ) { +%           my $args = 'key=' . $i->key . ";agentnum=$agentnum;locale=$locale";              <tr>                <td bgcolor='#ffffff'>                  <% $conf->exists($i->key, $agentnum) -                     ? '<img src="config-image.cgi?key='.      $i->key. -                                                 ';agentnum='. $agentnum. '">' +                     ? '<img src="config-image.cgi?'.$args.'">'                       : 'empty'                  %>                </td> @@ -141,18 +174,19 @@ Click on a configuration value to change it.              <tr>                <td>                  <% $conf->exists($i->key, $agentnum) -                     ? qq!<a href="config-download.cgi?key=!. $i->key. ';agentnum='. $agentnum. qq!">download</a>! +                     ? '<a href="config-download.cgi?'.$args.'">download</a>'                       : ''                  %>                </td>              </tr>  %   } elsif ( $type eq 'binary' ) { +%           my $args = 'key=' . $i->key . ";agentnum=$agentnum;locale=$locale";              <tr>                <td>                  <% $conf->exists($i->key, $agentnum) -                     ? qq!<a href="config-download.cgi?key=!. $i->key. ';agentnum='. $agentnum. qq!">download</a>! +                     ? '<a href="config-download.cgi?'.$args.'">download</a>'                       : 'empty'                  %>                </td> @@ -344,14 +378,37 @@ if ($cgi->param('agentnum') =~ /^(\d+)$/) {    die "Agent $page_agentnum not found!" unless $page_agent;    push @menubar, 'View all agents' => $p.'browse/agent.cgi'; +} + +my $conf = new FS::Conf; +my $conf_global = $conf; + +my @locales = $conf_global->config('available-locales'); + +# if this is set, we are in locale mode, so limit the displayed items  +# to those with per_locale. +my $locale; +my $locale_desc; +if ( $cgi->param('locale') =~ /^\w+_\w+$/ ) { +  $locale = $cgi->param('locale'); +  # and set the context on $conf +  $conf = new FS::Conf { 'locale' => $locale, 'localeonly' => 1 }; +  my %locale_info = FS::Locales->locale_info($locale); +  $locale_desc = "$locale_info{name} ($locale_info{country})"; + +  $title = 'Invoice Configuration'; #for now it is only invoicing +  $title .= ' for '.$page_agent->agent if $page_agent; +  $title .= ', '.$locale_desc; + +} elsif ($page_agent) {    $title = 'Agent Configuration for '. $page_agent->agent; +  $title .= ", $locale_desc" if $locale;  } else {    $title = 'Global Configuration';  } -my $conf = new FS::Conf; -  -my @config_items = grep { $page_agent ? $_->per_agent : 1 } +my @config_items = grep { !defined($locale) or $_->per_locale } +                   grep { $page_agent ? $_->per_agent : 1 }                     grep { $page_agent ? 1 : !$_->agentonly }                          $conf->config_items;  diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi index 040ed0403..6a1eaecf7 100644 --- a/httemplate/config/config.cgi +++ b/httemplate/config/config.cgi @@ -24,6 +24,7 @@ function SafeOnsubmit() {  <FORM NAME="OneTrueForm" ACTION="config-process.cgi" METHOD="POST" enctype="multipart/form-data" onSubmit="SafeOnsubmit()">  <INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agentnum %>"> +<INPUT TYPE="hidden" NAME="locale" VALUE="<% $locale %>">  <INPUT TYPE="hidden" NAME="key" VALUE="<% $key %>">  Setting <b><% $key %></b> @@ -49,7 +50,8 @@ Setting <b><% $key %></b>    <% $conf->exists($key, $agentnum)         ? 'Current image<br>'.           '<img src="config-image.cgi?key='.      $key. -                                   ';agentnum='. $agentnum. '"><br>' +                                   ';agentnum='. $agentnum. +                                   ';locale='.   $locale .'"><br>'         : ''    %> @@ -318,10 +320,6 @@ Setting <b><% $key %></b>  </HTML>  <%once> -my $conf = new FS::Conf; -my @config_items = $conf->config_items;  -my %confitems = map { $_->key => $_ } @config_items; -  my %element_types = map { $_ => 1 } qw(    select-part_svc select-part_pkg select-pkg_class select-agent  ); @@ -339,6 +337,15 @@ if ($cgi->param('agentnum') =~ /(\d+)$/) {    $agentnum=$1;  } +my $locale = ''; +if ( $cgi->param('locale') =~ /^(\w+_\w+)$/) { +  $locale = $1; +} + +my $conf = new FS::Conf { 'locale' => $locale, 'localeonly' => 1 }; +my @config_items = $conf->config_items;  +my %confitems = map { $_->key => $_ } @config_items; +  my $agent = '';  my $title;  if ($agentnum) { diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html index f2d6271d0..294104b09 100644 --- a/httemplate/edit/cust_main/billing.html +++ b/httemplate/edit/cust_main/billing.html @@ -555,6 +555,24 @@ function toggle(obj) {        <INPUT TYPE="hidden" NAME="cdr_termination_percentage" VALUE="<% $cust_main->cdr_termination_percentage %>">  % } +%my @available_locales = $conf->config('available-locales'); +%if ( scalar(@available_locales) ) { +%   push @available_locales, ''; +%    my %locale_labels = map {  +%        my %ll; +%        my %info = FS::Locales->locale_info($_); +%        $ll{$_} = $info{name} . " (" . $info{country} . ")"; +%        %ll; +%    } FS::Locales->locales; +  <& /elements/tr-select.html,  +        'label'         => emt('Invoicing locale'), +        'field'         => 'locale', +        'options'       => \@available_locales, +        'labels'        => \%locale_labels, +        'curr_value'    => $cust_main->locale, +  &> +% } +    </TABLE>    <% $r %> <% mt('required fields') |h %>  diff --git a/httemplate/view/cust_bill-logo.cgi b/httemplate/view/cust_bill-logo.cgi index ad2ff5430..75321ef82 100755 --- a/httemplate/view/cust_bill-logo.cgi +++ b/httemplate/view/cust_bill-logo.cgi @@ -5,7 +5,7 @@ die "access denied"    unless $FS::CurrentUser::CurrentUser->access_right('View invoices')        or $FS::CurrentUser::CurrentUser->access_right('Configuration'); -my $conf = new FS::Conf; +my $conf;  my $templatename;  my $agentnum = ''; @@ -13,6 +13,7 @@ if ( $cgi->param('invnum') ) {    $templatename = $cgi->param('template') || $cgi->param('templatename');    my $cust_bill = qsearchs('cust_bill', { 'invnum' => $cgi->param('invnum') } )      or die 'unknown invnum'; +  $conf = $cust_bill->conf;    $agentnum = $cust_bill->cust_main->agentnum;  } else {    my($query) = $cgi->keywords; diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html index cf22ff701..09c0b4d1d 100644 --- a/httemplate/view/cust_main/billing.html +++ b/httemplate/view/cust_main/billing.html @@ -273,6 +273,15 @@    </TR>  % } +% if ( $cust_main->locale ) { +% my %locale_info = FS::Locales->locale_info($cust_main->locale); +  <TR> +    <TD ALIGN="right"><% mt('Invoicing locale') |h %></TD> +    <TD BGCOLOR="#ffffff"><% $locale_info{name} . " (" . $locale_info{country} .")" %></TD> +  </TR> +% } + +  </TABLE></TD></TR></TABLE>  <%once>  | 
