=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);
}
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 {
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;
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;
'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,
},
{
'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,
},
{
'description' => 'Footer for HTML invoices. Defaults to the same data in invoice_latexfooter if not specified.',
'type' => 'textarea',
'per_agent' => 1,
+ 'per_locale' => 1,
},
{
'description' => 'Summary initial page for HTML invoices.',
'type' => 'textarea',
'per_agent' => 1,
+ 'per_locale' => 1,
},
{
'section' => 'invoicing',
'description' => 'Return address for HTML invoices. Defaults to the same data in invoice_latexreturnaddress if not specified.',
'type' => 'textarea',
+ 'per_locale' => 1,
},
{
'description' => 'Notes section for LaTeX typeset PostScript invoices.',
'type' => 'textarea',
'per_agent' => 1,
+ 'per_locale' => 1,
},
{
'description' => 'Footer for LaTeX typeset PostScript invoices.',
'type' => 'textarea',
'per_agent' => 1,
+ 'per_locale' => 1,
},
{
'description' => 'Summary initial page for LaTeX typeset PostScript invoices.',
'type' => 'textarea',
'per_agent' => 1,
+ 'per_locale' => 1,
},
{
'description' => 'Remittance coupon for LaTeX typeset PostScript invoices.',
'type' => 'textarea',
'per_agent' => 1,
+ 'per_locale' => 1,
},
{
'description' => 'Optional small footer for multi-page LaTeX typeset PostScript invoices.',
'type' => 'textarea',
'per_agent' => 1,
+ 'per_locale' => 1,
},
{
'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)
+ },
},
{
'type' => 'image',
'per_agent' => 1, #XXX just view/logo.cgi, which is for the global
#old-style editor anyway...?
+ 'per_locale' => 1,
},
{
'description' => 'Company logo for printed and PDF invoices, in EPS format.',
'type' => 'image',
'per_agent' => 1, #XXX as above, kinda
+ 'per_locale' => 1,
},
{
'type' => 'checkbox',
},
+ {
+ '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' => '',
--- /dev/null
+package FS::L10N::en_ca;
+use base qw(FS::L10N::en_us);
+
+1;
package FS::L10N::en_us;
use base qw(FS::L10N);
-#use strict;
our %Lexicon = ( _AUTO=>1 );
--- /dev/null
+package FS::L10N::fr_ca;
+use base qw(FS::L10N::fr_fr);
+
+1;
--- /dev/null
+package FS::L10N::fr_fr;
+use base qw(FS::L10N::DBI);
+
+our %Lexicon = ( _AUTO => 1 );
+
+1;
=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 {
%{ $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
'accountcode_cdr', 'char', 'NULL', 1, '', '',
'billday', 'int', 'NULL', '', '', '',
'edit_subject', 'char', 'NULL', 1, '', '',
+ 'locale', 'varchar', 'NULL', 16, '', '',
],
'primary_key' => 'custnum',
'unique' => [ [ 'agentnum', 'agent_custid' ] ],
'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' => [],
},
use strict;
use vars qw( @ISA );
use FS::Record;
+use FS::Locales;
@ISA = qw(FS::Record);
|| $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;
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;
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 );
#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
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 {
sub apply_payments_and_credits {
my( $self, %options ) = @_;
+ my $conf = $self->conf;
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
my $self = shift;
my %args = @_;
+ my $conf = $self->conf;
my $me = '[FS::cust_bill::generate_email]';
my $alternative = build MIME::Entity
'Type' => 'multipart/alternative',
- 'Encoding' => '7bit',
+ #'Encoding' => '7bit',
'Disposition' => 'inline'
;
$alternative->attach(
'Type' => 'text/plain',
- #'Encoding' => 'quoted-printable',
- 'Encoding' => '7bit',
+ 'Encoding' => 'quoted-printable',
+ #'Encoding' => '7bit',
'Data' => $data,
'Disposition' => 'inline',
);
sub send {
my $self = shift;
+ my $conf = $self->conf;
my( $template, $invoice_from, $notice_name );
my $agentnums = '';
#sub email_invoice {
sub email {
my $self = shift;
+ my $conf = $self->conf;
my( $template, $invoice_from, $notice_name, $no_coupon );
if ( ref($_[0]) ) {
sub email_subject {
my $self = shift;
+ my $conf = $self->conf;
#my $template = scalar(@_) ? shift : '';
#per-template?
sub lpr_data {
my $self = shift;
+ my $conf = $self->conf;
my( $template, $notice_name );
if ( ref($_[0]) ) {
my $opt = shift;
#sub print_invoice {
sub print {
my $self = shift;
+ my $conf = $self->conf;
my( $template, $notice_name );
if ( ref($_[0]) ) {
my $opt = shift;
sub fax_invoice {
my $self = shift;
+ my $conf = $self->conf;
my( $template, $notice_name );
if ( ref($_[0]) ) {
my $opt = shift;
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
sub ftp_invoice {
my $self = shift;
+ my $conf = $self->conf;
my $template = scalar(@_) ? shift : '';
$self->send_csv(
sub spool_invoice {
my $self = shift;
+ my $conf = $self->conf;
my $template = scalar(@_) ? shift : '';
$self->spool_csv(
sub realtime_bop {
my( $self, $method ) = (shift,shift);
+ my $conf = $self->conf;
my %opt = @_;
my $cust_main = $self->cust_main;
sub print_latex {
my $self = shift;
+ my $conf = $self->conf;
my( $today, $template, %opt );
if ( ref($_[0]) ) {
%opt = %{ shift() };
SUFFIX => '.tex',
UNLINK => 0,
) or die "can't open temp file: $!\n";
+ binmode($fh, ':utf8'); # language support
print $fh join('', @filled_in );
close $fh;
# 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;
'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 ) {
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' => '',
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' : '',
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' : '',
};
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' : '',
};
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, ['',''];
}
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 );
$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'
),
{
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');
&$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;
}
#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{
sub terms {
my $self = shift;
+ my $conf = $self->conf;
#check for an invoice-specific override
return $self->invoice_terms if $self->invoice_terms;
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;
}
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*$/ ) {
$duedate;
}
-sub credit_balance_msg { 'Credit Balance Remaining' }
+sub credit_balance_msg {
+ my $self = shift;
+ $self->mt('Credit Balance Remaining')
+}
=item invnum_date_pretty
sub invnum_date_pretty {
my $self = shift;
- 'Invoice #'. $self->invnum. ' ('. $self->_date_pretty. ')';
+ $self->mt('Invoice #'). $self->invnum. ' ('. $self->_date_pretty. ')';
}
=item _date_pretty
sub _items_extra_usage_sections {
my $self = shift;
+ my $conf = $self->conf;
my $escape = shift;
my $format = shift;
sub _items_svc_phone_sections {
my $self = shift;
+ my $conf = $self->conf;
my $escape = shift;
my $format = shift;
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 = ();
? '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),
sub _items_cust_bill_pkg {
my $self = shift;
+ my $conf = $self->conf;
my $cust_bill_pkgs = shift;
my %opt = @_;
#'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),
};
#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 )
};
=cut
sub due_date_sql {
+ my $conf = new FS::Conf;
'COALESCE(
SUBSTRING(
COALESCE(
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
|| $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.
}
+=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
<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
);
{
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();
$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 = (
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 : '';
$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"));
}
sub ssh_cmd { #subroutine, not method
+ # XXX shouldn't this use $opt->{'stdin_string'} at some point?
use Net::OpenSSH;
my $opt = { @_ };
my $ssh = Net::OpenSSH->new($opt->{'user'}.'@'.$opt->{'host'});
.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>
<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>
%>
</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,
$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>
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%">'.
$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>';
} 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>';
}
\r
\usepackage{fancyhdr,lastpage,ifthen,array,fslongtable,afterpage,caption,multirow,bigstrut}\r
\usepackage{graphicx} % required for logo graphic\r
+\usepackage[utf8]{inputenc} % multilanguage support\r
+\usepackage[T1]{fontenc}\r
\r
\addtolength{\voffset}{-0.0cm} % top margin to top of header\r
\addtolength{\hoffset}{-0.6cm} % left margin on page\r
\ifthenelse{\equal{\thepage}{1}}\r
{ % First page\r
\begin{tabular}{ccc}\r
- Invoice date & Invoice \#& Customer \#\\\r
+ [@-- join(' & ', emt('Invoice date'), emt('Invoice #'), emt('Customer #') ) --@]\\\r
\vspace{0.2cm}\r
\textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]} & \textbf{[@-- $custnum --@]} \\\hline\r
- \rule{0pt}{5ex} &~~ \huge{\textsc{[@-- $notice_name || 'Invoice' --@]}} & \\\r
+ \rule{0pt}{5ex} &~~ \huge{\textsc{[@-- emt($notice_name) --@]}} & \\\r
\vspace{-0.2cm}\r
& & \\\hline\r
\end{tabular}\r
{ % ... pages\r
\small{\r
\begin{tabular}{lll}\r
- Invoice date & Invoice \#& Customer\#\\\r
+ [@-- join(' & ', emt('Invoice date'), emt('Invoice #'), emt('Customer #') ) --@]\\\r
\textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]} & \textbf{[@-- $custnum --@]}\\\r
\end{tabular}\r
}\r
\r
\newcommand{\FSdescriptionlength} { [@-- $unitprices ? '8.2cm' : '12.8cm' --@] }\r
\newcommand{\FSdescriptioncolumncount} { [@-- $unitprices ? '4' : '6' --@] }\r
-\newcommand{\FSunitcolumns}{ [@-- $unitprices ? '\makebox[2.5cm][l]{\textbf{~~Unit Price}}&\makebox[1.4cm]{\textbf{~Quantity}}&' : '' --@] }\r
+\newcommand{\FSunitcolumns}{ [@-- \r
+ $unitprices \r
+ ? '\makebox[2.5cm][l]{\textbf{~~'.emt('Unit Price').'}}&\makebox[1.4cm]{\textbf{~'.emt('Quantity').'}}&' \r
+ : '' --@] }\r
\r
\newcommand{\FShead}{\r
\hline\r
\rule{0pt}{2.5ex}\r
\makebox[1.4cm]{\textbf{Ref}} &\r
-% \makebox[2.9cm][l]{\textbf{Description}}&\r
-% \makebox[1.4cm][l]{}&\r
-% \makebox[1.4cm][l]{}&\r
-% \makebox[2.5cm][l]{}&\r
- \multicolumn{\FSdescriptioncolumncount}{l}{\makebox[\FSdescriptionlength][l]{\textbf{Description}}}&\r
+ \multicolumn{\FSdescriptioncolumncount}{l}{\makebox[\FSdescriptionlength][l]{\textbf{[@-- emt('Description') --@]}}}&\r
\FSunitcolumns\r
- \makebox[1.6cm][r]{\textbf{Amount}} \\\r
+ \makebox[1.6cm][r]{\textbf{[@-- emt('Amount') --@]}} \\\r
\hline\r
}\r
\r
\begin{minipage}[t]{6.4cm}\r
[@--\r
if ($ship_enable) {\r
- $OUT .= '\textbf{Service Address}\\\\';\r
+ $OUT .= '\textbf{' . emt('Service Address') . '}\\\\';\r
$OUT .= "\\addressline{$ship_company}";\r
$OUT .= "\\addressline{$ship_address1}";\r
$OUT .= "\\addressline{$ship_address2}";\r
}\r
--@]\r
\begin{flushright}\r
-[@-- $terms ? "Terms: $terms" : '' --@]\\\r
+[@-- $terms ? emt('Terms') .": $terms" : '' --@]\\\r
[@-- $po_line --@]\\\r
\end{flushright}\r
\end{minipage}}\r
if $coupon;\r
$OUT .= '\begin{longtable}{cllllllr}';\r
$OUT .= '\caption*{ ';\r
- $OUT .= ($section->{'description'}) ? $section->{'description'}: 'Charges';\r
+ $OUT .= ($section->{'description'}) ? $section->{'description'}: emt('Charges');\r
$OUT .= '}\\\\';\r
if ($section->{header_generator}) {\r
$OUT .= &{$section->{header_generator}}();\r
$OUT .= '\FShead';\r
}\r
$OUT .= '\endfirsthead';\r
- $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\\\';\r
+ $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued from previous page').'}\\\\';\r
if ($section->{header_generator}) {\r
$OUT .= &{$section->{header_generator}}();\r
} else {\r
$OUT .= '\FShead';\r
}\r
$OUT .= '\endhead';\r
- $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}Continued on next page...}\\\\';\r
+ $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued on next page...').'}\\\\';\r
$OUT .= '\endfoot';\r
$OUT .= '\hline';\r
\r
- { $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; }
-%
-%
-%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');
$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>
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";
$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;
% }
%
%} 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 ) {
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'));
% }
%
% }
+% 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') %>
% 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',
</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>
<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>
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;
<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>
<% $conf->exists($key, $agentnum)
? 'Current image<br>'.
'<img src="config-image.cgi?key='. $key.
- ';agentnum='. $agentnum. '"><br>'
+ ';agentnum='. $agentnum.
+ ';locale='. $locale .'"><br>'
: ''
%>
</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
);
$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) {
<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 %>
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 = '';
$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;
</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>