use IO::File;
use File::Basename;
use MIME::Base64;
+use Locale::Currency;
use FS::ConfItem;
use FS::ConfDefaults;
use FS::Conf_compat17;
{
'key' => 'currency',
'section' => 'billing',
- 'description' => 'Currency',
+ 'description' => 'Main accounting currency',
'type' => 'select',
'select_enum' => [ '', qw( USD AUD CAD DKK EUR GBP ILS JPY NZD XAF ) ],
},
{
+ 'key' => 'currencies',
+ 'section' => 'billing',
+ 'description' => 'Additional accepted currencies',
+ 'type' => 'select-sub',
+ 'multiple' => 1,
+ 'options_sub' => sub {
+ map { $_ => code2currency($_) } all_currency_codes();
+ },
+ 'sort_sub' => sub ($$) { $_[0] cmp $_[1]; },
+ 'option_sub' => sub { code2currency(shift); },
+ },
+
+ {
'key' => 'business-batchpayment-test_transaction',
'section' => 'billing',
'description' => 'Turns on the Business::BatchPayment test_mode flag. Note that not all gateway modules support this flag; if yours does not, using the batch gateway will fail.',
use HTML::Widgets::SelectLayers 0.07; #should go away in favor of
#selectlayers.html
use Locale::Country;
+ use Locale::Currency;
+ use Locale::Currency::Format;
use Business::US::USPS::WebTools::AddressStandardization;
use Geo::GoogleEarth::Pluggable;
use LWP::UserAgent;
use FS::part_pkg_msgcat;
use FS::svc_cable;
use FS::cable_device;
+ use FS::agent_currency;
+ use FS::currency_exchange;
+ use FS::part_pkg_currency;
# Sammath Naur
if ( $FS::Mason::addl_handler_use ) {
use Exporter;
use Carp qw(carp cluck croak confess);
use Scalar::Util qw( blessed );
+use File::Slurp qw( slurp );
use File::CounterFile;
-use Locale::Country;
use Text::CSV_XS;
-use File::Slurp qw( slurp );
use DBI qw(:sql_types);
use DBIx::DBSchema 0.38;
+use Locale::Country;
+use Locale::Currency;
+use NetAddr::IP; # for validation
use FS::UID qw(dbh datasrc driver_name);
use FS::CurrentUser;
use FS::Schema qw(dbdef);
use FS::SearchCache;
use FS::Msgcat qw(gettext);
-use NetAddr::IP; # for validation
-use Data::Dumper;
#use FS::Conf; #dependency loop bs, in install_callback below instead
use FS::part_virtual_field;
=cut
+use Data::Dumper;
sub batch_import {
my $param = shift;
$self->ut_money($field);
}
+=item ut_currencyn COLUMN
+
+Check/untaint currency indicators, such as USD or EUR. May be null. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_currencyn {
+ my($self, $field) = @_;
+ if ($self->getfield($field) eq '') { #can be null
+ $self->setfield($field, '');
+ return '';
+ }
+ $self->ut_currency($field);
+}
+
+=item ut_currency COLUMN
+
+Check/untaint currency indicators, such as USD or EUR. May not be null. If
+there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_currency {
+ my($self, $field) = @_;
+ my $value = uc( $self->getfield($field) );
+ if ( code2currency($value) ) {
+ $self->setfield($value);
+ } else {
+ return "Unknown currency $value";
+ }
+
+ '';
+}
+
=item ut_text COLUMN
Check/untaint text. Alphanumerics, spaces, and the following punctuation
'index' => [ ['salesnum'], ['disabled'] ],
},
+ 'agent_currency' => {
+ 'columns' => [
+ 'agentcurrencynum', 'serial', '', '', '', '',
+ 'agentnum', 'int', '', '', '', '',
+ 'currency', 'char', '', 3, '', '',
+ ],
+ 'primary_key' => 'agentcurrencynum',
+ 'unique' => [],
+ 'index' => [ ['agentnum'] ],
+ },
+
'cust_attachment' => {
'columns' => [
'attachnum', 'serial', '', '', '', '',
'index' => [],
},
+ 'part_pkg_currency' => {
+ 'columns' => [
+ 'pkgcurrencynum', 'serial', '', '', '', '',
+ 'pkgpart', 'int', '', '', '', '',
+ 'currency', 'char', '', 3, '', '',
+ 'optionname', 'varchar', '', $char_d, '', '',
+ 'optionvalue', 'text', '', '', '', '',
+ ],
+ 'primary_key' => 'pkgcurrencynum',
+ 'unique' => [ [ 'pkgpart', 'currency', 'optionname' ] ],
+ 'index' => [ ['pkgpart'] ],
+ },
+
+ 'currency_exchange' => {
+ 'columns' => [
+ 'currencyratenum', 'serial', '', '', '', '',
+ 'from_currency', 'char', '', 3, '', '',
+ 'to_currency', 'char', '', 3, '', '',
+ 'rate', 'decimal', '', '7,6', '', '',
+ ],
+ 'primary_key' => 'currencyratenum',
+ 'unique' => [ [ 'from_currency', 'to_currency' ] ],
+ 'index' => [],
+ },
+
'part_pkg_link' => {
'columns' => [
'pkglinknum', 'serial', '', '', '', '',
package FS::agent;
+use base qw( FS::m2m_Common FS::m2name_Common FS::Record );
use strict;
use vars qw( @ISA );
-#use Crypt::YAPassGen;
use Business::CreditCard 0.28;
use FS::Record qw( dbh qsearch qsearchs );
use FS::cust_main;
use FS::cust_pkg;
use FS::agent_type;
+use FS::agent_currency;
use FS::reg_code;
use FS::TicketSystem;
use FS::Conf;
-@ISA = qw( FS::m2m_Common FS::Record );
-
=head1 NAME
FS::agent - Object methods for agent records
qsearchs( 'cust_main', { 'custnum' => $self->agent_custnum } );
}
+=item agent_currency
+
+Returns the FS::agent_currency objects (see L<FS::agent_currency>), if any, for
+this agent.
+
+=cut
+
+sub agent_currency {
+ my $self = shift;
+ qsearch('agent_currency', { 'agentnum' => $self->agentnum } );
+}
+
+=item agent_currency_hashref
+
+Returns a hash references of supported additional currencies for this agent.
+
+=cut
+
+sub agent_currency_hashref {
+ my $self = shift;
+ +{ map { $_->currency => 1 }
+ $self->agent_currency
+ };
+}
+
=item pkgpart_hashref
Returns a hash reference. The keys of the hash are pkgparts. The value is
--- /dev/null
+package FS::agent_currency;
+use base qw( FS::Record );
+
+use strict;
+#use FS::Record qw( qsearch qsearchs );
+use FS::agent;
+
+=head1 NAME
+
+FS::agent_currency - Object methods for agent_currency records
+
+=head1 SYNOPSIS
+
+ use FS::agent_currency;
+
+ $record = new FS::agent_currency \%hash;
+ $record = new FS::agent_currency { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::agent_currency object represents an agent's ability to sell
+in a specific non-default currency. FS::agent_currency inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item agentcurrencynum
+
+primary key
+
+=item agentnum
+
+Agent (see L<FS::agent>)
+
+=item currency
+
+3 letter currency code
+
+=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<hash> method.
+
+=cut
+
+sub table { 'agent_currency'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=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.
+
+=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
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('agentcurrencynum')
+ || $self->ut_foreign_key('agentnum', 'agent', 'agentnum')
+ || $self->ut_currency('currency')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::agent>
+
+=cut
+
+1;
+
--- /dev/null
+package FS::currency_exchange;
+use base qw( FS::Record );
+
+use strict;
+#use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::currency_exchange - Object methods for currency_exchange records
+
+=head1 SYNOPSIS
+
+ use FS::currency_exchange;
+
+ $record = new FS::currency_exchange \%hash;
+ $record = new FS::currency_exchange { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::currency_exchange object represents an exchange rate between currencies.
+FS::currency_exchange inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item currencyratenum
+
+primary key
+
+=item from_currency
+
+from_currency
+
+=item to_currency
+
+to_currency
+
+=item rate
+
+rate
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new exchange rate. To add the exchange rate 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<hash> method.
+
+=cut
+
+sub table { 'currency_exchange'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=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.
+
+=item check
+
+Checks all fields to make sure this is a valid exchange rate. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('currencyratenum')
+ || $self->ut_currency('from_currency')
+ || $self->ut_currency('to_currency')
+ || $self->ut_float('rate') #good enough for untainting
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
use FS::part_pkg_discount;
use FS::part_pkg_usage;
use FS::part_pkg_vendor;
+use FS::part_pkg_currency;
$DEBUG = 0;
$setup_hack = 0;
If I<options> is set to a hashref of options, appropriate FS::part_pkg_option
records will be inserted.
+If I<part_pkg_currency> is set to a hashref of options (with the keys as
+option_CURRENCY), appropriate FS::part_pkg::currency records will be inserted.
+
=cut
sub insert {
}
}
+ warn " inserting part_pkg_currency records" if $DEBUG;
+ my %part_pkg_currency = %{ $options{'part_pkg_currency'} || {} };
+ foreach my $key ( keys %part_pkg_currency ) {
+ $key =~ /^(.+)_([A-Z]{3})$/ or next;
+ my $part_pkg_currency = new FS::part_pkg_currency {
+ 'pkgpart' => $self->pkgpart,
+ 'optionname' => $1,
+ 'currency' => $2,
+ 'optionvalue' => $part_pkg_currency{$key},
+ };
+ my $error = $part_pkg_currency->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
unless ( $skip_pkg_svc_hack ) {
warn " inserting pkg_svc records" if $DEBUG;
If I<options> is set to a hashref, the appropriate FS::part_pkg_option records
will be replaced.
+If I<part_pkg_currency> is set to a hashref of options (with the keys as
+option_CURRENCY), appropriate FS::part_pkg::currency records will be replaced.
+
=cut
sub replace {
}
}
+ #trivial nit: not the most efficient to delete and reinsert
+ warn " deleting old part_pkg_currency records" if $DEBUG;
+ foreach my $part_pkg_currency ( $old->part_pkg_currency ) {
+ my $error = $part_pkg_currency->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error deleting part_pkg_currency record: $error";
+ }
+ }
+
+ warn " inserting new part_pkg_currency records" if $DEBUG;
+ my %part_pkg_currency = %{ $options->{'part_pkg_currency'} || {} };
+ foreach my $key ( keys %part_pkg_currency ) {
+ $key =~ /^(.+)_([A-Z]{3})$/ or next;
+ my $part_pkg_currency = new FS::part_pkg_currency {
+ 'pkgpart' => $new->pkgpart,
+ 'optionname' => $1,
+ 'currency' => $2,
+ 'optionvalue' => $part_pkg_currency{$key},
+ };
+ my $error = $part_pkg_currency->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting part_pkg_currency record: $error";
+ }
+ }
+
+
warn " replacing pkg_svc records" if $DEBUG;
my $pkg_svc = $options->{'pkg_svc'};
my $hidden_svc = $options->{'hidden_svc'} || {};
'';
}
+=item part_pkg_currency [ CURRENCY ]
+
+Returns all currency options as FS::part_pkg_currency objects (see
+L<FS::part_pkg_currency>), or, if a currency is specified, only return the
+objects for that currency.
+
+=cut
+
+sub part_pkg_currency {
+ my $self = shift;
+ my %hash = ( 'pkgpart' => $self->pkgpart );
+ $hash{'currency'} = shift if @_;
+ qsearch('part_pkg_currency', \%hash );
+}
+
+=item part_pkg_currency_options CURRENCY
+
+Returns a list of option names and values from FS::part_pkg_currency for the
+specified currency.
+
+=cut
+
+sub part_pkg_currency_options {
+ my $self = shift;
+ map { $_->optionname => $_->optionvalue } $self->part_pkg_currency(shift);
+}
+
=item bill_part_pkg_link
Returns the associated part_pkg_link records (see L<FS::part_pkg_link>).
--- /dev/null
+package FS::part_pkg_currency;
+use base qw( FS::Record );
+
+use strict;
+#use FS::Record qw( qsearch qsearchs );
+use FS::part_pkg;
+
+=head1 NAME
+
+FS::part_pkg_currency - Object methods for part_pkg_currency records
+
+=head1 SYNOPSIS
+
+ use FS::part_pkg_currency;
+
+ $record = new FS::part_pkg_currency \%hash;
+ $record = new FS::part_pkg_currency { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_pkg_currency object represents an example. FS::part_pkg_currency inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item pkgcurrencynum
+
+primary key
+
+=item pkgpart
+
+Package definition (see L<FS::part_pkg>).
+
+=item currency
+
+3-letter currency code
+
+=item optionname
+
+optionname
+
+=item optionvalue
+
+optionvalue
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example. To add the example 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<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'part_pkg_currency'; }
+
+=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 example. 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('pkgcurrencynum')
+ || $self->ut_foreign_key('pkgpart', 'part_pkg', 'pkgpart')
+ || $self->ut_currency('currency')
+ || $self->ut_text('optionname')
+ || $self->ut_textn('optionvalue')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+The author forgot to customize this manpage.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
t/cable_device.t
FS/h_svc_cable.pm
t/h_svc_cable.t
+FS/agent_currency.pm
+t/agent_currency.t
+FS/currency_exchange.pm
+t/currency_exchange.t
+FS/part_pkg_currency.pm
+t/part_pkg_currency.t
--- /dev/null
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::agent_currency;
+$loaded=1;
+print "ok 1\n";
--- /dev/null
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::currency_exchange;
+$loaded=1;
+print "ok 1\n";
--- /dev/null
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg_currency;
+$loaded=1;
+print "ok 1\n";
<TH CLASS="grid" BGCOLOR="#cccccc">Ticketing</TH>
% }
+% if ( $conf->config('currencies') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Currencies</TH>
+% }
+
<TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Payment Gateway Overrides</FONT></TH>
<TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Configuration Overrides</FONT></TH>
</TR>
<BR><A HREF="<%$p%>edit/prepay_credit.cgi?agentnum=<% $agent->agentnum %>">Generate cards</A>
</TD>
-% if ( $conf->config('ticket_system') ) {
-
+% if ( $conf->config('ticket_system') ) {
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
-% if ( $agent->ticketing_queueid ) {
-
- Queue: <% $agent->ticketing_queueid %>: <% $agent->ticketing_queue %><BR>
+% if ( $agent->ticketing_queueid ) {
+ Queue: <% $agent->ticketing_queueid %>:
+ <% $agent->ticketing_queue %>
+ <BR>
+% }
+ </TD>
% }
+% if ( $conf->config('currencies') ) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% join('<BR>', sort keys %{ $agent->agent_currency_hashref } ) %>
</TD>
% }
-
<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>
% foreach my $override (
% }
% my %options = &{$config_item->options_sub};
-% my @options = sort { $a <=> $b } keys %options;
+% my @options = keys %options;
+% my $sortsub = $config_item->sort_sub || sub { $a <=> $b };
+% @options = sort $sortsub @options;
% my %saw;
% foreach my $value ( @options ) {
% local($^W)=0; next if $saw{$value}++;
% }
</TABLE>
+<BR>
+
+% if ( $conf->config('currencies') ) {
+
+ <FONT CLASS="fsinnerbox-title"><% mt('Currencies') |h %></FONT>
+ <TABLE CLASS="fsinnerbox">
+ <TR>
+ <TD>
+ <& /elements/checkboxes-table-name.html,
+ 'link_table' => 'agent_currency',
+ 'name_col' => 'currency',
+ 'names_list' => [ map [ $_, {label=>"$_: ".code2currency($_)} ],
+ $conf->config('currencies')
+ ],
+ &>
+ </TD>
+ </TR>
+ </TABLE>
+% }
<BR>
+
+
<INPUT TYPE="submit" VALUE="<% $agent->agentnum ? "Apply changes" : "Add agent" %>">
</FORM>
--- /dev/null
+<& /elements/header.html, 'Exchange rates' &>
+
+<FORM METHOD="POST" ACTION="process/currency_exchange.html">
+
+<& /elements/table-grid.html &>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">From</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Rate</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">To</TH>
+</TR>
+
+%foreach my $currency (@currencies) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my %hash = ( 'from_currency' => $currency,
+% 'to_currency' => $to_currency,
+% );
+%
+% my $currency_exchange = qsearchs('currency_exchange', \%hash)
+% || new FS::currency_exchange \%hash;
+%
+% $currency_exchange->rate('1.000000') if length($currency_exchange->rate) == 0;
+
+ <TR>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $currency %>: <% code2currency($currency) %>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+ <INPUT TYPE = "text"
+ NAME = "<% "$currency-$to_currency" %>"
+ VALUE = "<% $currency_exchange->rate %>"
+ SIZE = 14
+ MAXLENGTH = 14
+ >
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $to_currency %>: <% code2currency($to_currency) %>
+ </TD>
+
+ </TR>
+% }
+
+ </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Update rates">
+</FORM>
+
+<& /elements/footer.html &>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+my $to_currency = $conf->config('currency') || 'USD';
+
+my @currencies = sort { $a cmp $b } $conf->config('currencies');
+
+</%init>
% #text and derivitives
% 'size' => $f->{'size'},
% 'maxlength' => $f->{'maxlength'},
+% 'prefix' => $f->{'prefix'},
% 'postfix' => $f->{'postfix'},
%
% #textarea
'setup_show_zero' => 'Show zero setup',
'recur_fee' => 'Recurring fee',
'recur_show_zero' => 'Show zero recurring',
+ ( map { ( "setup_fee_$_" => "Setup fee $_",
+ "recur_fee_$_" => "Recurring fee $_",
+ );
+ }
+ $conf->config('currencies')
+ ),
'discountnum' => 'Offer discounts for longer terms',
'bill_dst_pkgpart' => 'Include line item(s) from package',
'svc_dst_pkgpart' => 'Include services of package',
value => 'Y',
disabled => sub { $setup_show_zero_disabled },
},
+ ( map { +{ field => "setup_fee_$_",
+ type => 'text',
+ prefix=> currency_symbol($_, SYM_HTML),
+ size => 8,
+ }
+ }
+ sort $conf->config('currencies')
+ ),
{ field => 'freq',
type => 'part_pkg_freq',
onchange => 'freq_changed',
disabled => sub { $recur_disabled },
onchange => 'recur_changed',
},
-
{ field => 'recur_show_zero',
type => 'checkbox',
value => 'Y',
disabled => sub { $recur_show_zero_disabled },
},
+ ( map { +{ field => "recur_fee_$_",
+ type => 'text',
+ prefix=> currency_symbol($_, SYM_HTML),
+ size => 8,
+ }
+ }
+ sort $conf->config('currencies')
+ ),
#price plan
#setup fee
$object->set($_ => scalar($cgi->param($_)) )
foreach (qw( setup_fee recur_fee disable_line_item_date_ranges ));
+ foreach my $currency ( $conf->config('currencies') ) {
+ my %part_pkg_currency = $object->part_pkg_currency_options($currency);
+ foreach (qw( setup_fee recur_fee )) {
+ my $param = $_.'_'.$currency;
+ $object->set( $param, $cgi->param($param) );
+ }
+ }
+
$pkgpart = $object->pkgpart;
&$splice_locale_fields(
$object->set($_ => $object->option($_, 1))
foreach (qw( setup_fee recur_fee disable_line_item_date_ranges ));
+ foreach my $currency ( $conf->config('currencies') ) {
+ my %part_pkg_currency = $object->part_pkg_currency_options($currency);
+ $object->set( $_.'_'.$currency, $part_pkg_currency{$_} )
+ foreach keys %part_pkg_currency;
+ }
+
$pkgpart = $object->pkgpart;
&$splice_locale_fields(
$object->set($_ => $options{$_})
foreach (qw( setup_fee recur_fee disable_line_item_date_ranges ));
+ foreach my $currency ( $conf->config('currencies') ) {
+ my %part_pkg_currency = $object->part_pkg_currency_options($currency);
+ $object->set( $_.'_'.$currency, $part_pkg_currency{$_} )
+ foreach keys %part_pkg_currency;
+ }
+
$recur_disabled = $object->freq ? 0 : 1;
&$splice_locale_fields(
'process_m2m' => { 'link_table' => 'access_groupagent',
'target_table' => 'access_group',
},
+ 'process_m2name' => {
+ 'link_table' => 'agent_currency',
+ 'name_col' => 'currency',
+ 'names_list' => [ $conf->config('currencies') ],
+ 'param_style' => 'link_table.value checkboxes',
+ },
'edit_ext' => 'cgi',
'noerror_callback' => $process_agent_pkg_class,
)
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-if ( FS::Conf->new->exists('disable_acl_changes') ) {
+my $conf = new FS::Conf;
+
+if ( $conf->exists('disable_acl_changes') ) {
errorpage('ACL changes disabled in public demo.');
die "shouldn't be reached";
}
--- /dev/null
+%if ( $error ) {
+% errorpage($error); #also not super ideal
+%} else { #or this
+<% include('/elements/header.html', 'Exchange rates updated') %>
+<% include('/elements/footer.html') %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+my $to_currency = $conf->config('currency') || 'USD';
+
+my @currencies = sort { $a cmp $b } $conf->config('currencies');
+
+#in the best of all possible worlds, i would be a single database transaction
+# but here it isn't terribly important other than offending my sense of elegance
+my $error = '';
+foreach my $currency (@currencies) {
+
+ my %hash = ( 'from_currency' => $currency,
+ 'to_currency' => $to_currency,
+ );
+
+ my $currency_exchange = qsearchs('currency_exchange', \%hash)
+ || new FS::currency_exchange \%hash;
+
+ $currency_exchange->rate( $cgi->param("$currency-$to_currency") );
+
+ my $method = $currency_exchange->currencyratenum ? 'replace' : 'insert';
+ $error = $currency_exchange->$method() and last;
+}
+
+</%init>
push @args, 'options' => \%options;
###
+ #part_pkg_currency
+ ###
+
+ my %part_pkg_currency = (
+ map { $_ => scalar($cgi->param($_)) }
+ #grep /._[A-Z]{3}$/, #support other options
+ grep /^(setup|recur)_fee_[A-Z]{3}$/,
+ $cgi->param
+ );
+
+ push @args, 'part_pkg_currency' => \%part_pkg_currency;
+
+ ###
#pkg_svc
###
'name_col' => 'name_column',
#or
- 'name_callback' => sub { },
+ #not yet 'name_callback' => sub { },
'names_list' => [ 'value',
'other value',
# required
- #? 'name_callback' => sub { },
+ #not yet 'name_callback' => sub { },
'names_list' => [ 'value',
'other value',
if ( $curuser->access_right('Configuration') ) {
#$config_billing{'Invoice events'} = [ $fsurl.'browse/part_bill_event.cgi', 'Deprecated, old-style actions for overdue invoices' ];
$config_billing{'Invoice templates'} = [ $fsurl.'browse/invoice_template.html', 'Edit templates for HTML, plaintext and typeset invoices' ];
+ $config_billing{'separator'} = ''; #its a separator!
$config_billing{'Prepaid cards'} = [ $fsurl.'search/prepay_credit.html', 'View outstanding cards, generate new cards' ];
$config_billing{'Call rates and regions'} = [ \%config_billing_rates, 'Manage rate plans, regions and prefixes for VoIP and call billing' ];
+ $config_billing{'separator2'} = ''; #its a separator!
my $config_taxes_name = 'Locales and tax rates'.
( $conf->exists('enable_taxproducts')
if $conf->exists('enable_taxproducts');
$config_billing{'Tax classes'} = [ $fsurl. 'browse/part_pkg_taxclass.html', 'Tax classes' ];
+ if ( $conf->config('currencies') ) {
+ $config_billing{'separator3'} = ''; #its a separator!
+ $config_billing{'Exchange rates'} = [ $fsurl.'edit/currency_exchange.html', 'Currency exchange rates' ];
+ }
+
+ $config_billing{'separator4'} = ''; #its a separator!
$config_billing{'Credit reasons'} = [ $fsurl.'browse/reason.html?class=R', 'Credit reasons explain why a credit was issued.' ];
$config_billing{'Credit reason types'} = [ $fsurl.'browse/reason_type.html?class=R', 'Credit reason types define groups of reasons.' ];
}