From e96a2a6fd3a8885b0fb035ecc55bdf50dbe5a4aa Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Sat, 8 Jun 2013 01:30:52 -0700 Subject: [PATCH] multi-currency, RT#21565 --- FS/FS/Conf.pm | 16 ++- FS/FS/Mason.pm | 5 + FS/FS/Record.pm | 44 +++++++- FS/FS/Schema.pm | 36 +++++++ FS/FS/agent.pm | 30 +++++- FS/FS/agent_currency.pm | 110 +++++++++++++++++++ FS/FS/currency_exchange.pm | 116 +++++++++++++++++++++ FS/FS/part_pkg.pm | 79 ++++++++++++++ FS/FS/part_pkg_currency.pm | 139 +++++++++++++++++++++++++ FS/MANIFEST | 6 ++ FS/t/agent_currency.t | 5 + FS/t/currency_exchange.t | 5 + FS/t/part_pkg_currency.t | 5 + httemplate/browse/agent.cgi | 20 ++-- httemplate/config/config.cgi | 4 +- httemplate/edit/agent.cgi | 21 ++++ httemplate/edit/currency_exchange.html | 73 +++++++++++++ httemplate/edit/elements/edit.html | 1 + httemplate/edit/part_pkg.cgi | 43 +++++++- httemplate/edit/process/agent.cgi | 10 +- httemplate/edit/process/currency_exchange.html | 36 +++++++ httemplate/edit/process/part_pkg.cgi | 13 +++ httemplate/elements/checkboxes-table-name.html | 2 +- httemplate/elements/checkboxes.html | 2 +- httemplate/elements/menu.html | 8 ++ 25 files changed, 810 insertions(+), 19 deletions(-) create mode 100644 FS/FS/agent_currency.pm create mode 100644 FS/FS/currency_exchange.pm create mode 100644 FS/FS/part_pkg_currency.pm create mode 100644 FS/t/agent_currency.t create mode 100644 FS/t/currency_exchange.t create mode 100644 FS/t/part_pkg_currency.t create mode 100755 httemplate/edit/currency_exchange.html create mode 100644 httemplate/edit/process/currency_exchange.html diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index c85e4a593..982c3408d 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -5,6 +5,7 @@ use Carp; use IO::File; use File::Basename; use MIME::Base64; +use Locale::Currency; use FS::ConfItem; use FS::ConfDefaults; use FS::Conf_compat17; @@ -1006,12 +1007,25 @@ sub reason_type_options { { '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.', diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 90ced1f96..6c12e8110 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -121,6 +121,8 @@ if ( -e $addl_handler_use_file ) { 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; @@ -341,6 +343,9 @@ if ( -e $addl_handler_use_file ) { 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 ) { diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index cdbcae049..be355213f 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -12,19 +12,19 @@ use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG 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; @@ -1528,6 +1528,7 @@ csv, xls, fixedlength, xml =cut +use Data::Dumper; sub batch_import { my $param = shift; @@ -2129,6 +2130,41 @@ sub ut_moneyn { $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 diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 71d84cc68..048718641 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -533,6 +533,17 @@ sub tables_hashref { 'index' => [ ['salesnum'], ['disabled'] ], }, + 'agent_currency' => { + 'columns' => [ + 'agentcurrencynum', 'serial', '', '', '', '', + 'agentnum', 'int', '', '', '', '', + 'currency', 'char', '', 3, '', '', + ], + 'primary_key' => 'agentcurrencynum', + 'unique' => [], + 'index' => [ ['agentnum'] ], + }, + 'cust_attachment' => { 'columns' => [ 'attachnum', 'serial', '', '', '', '', @@ -2054,6 +2065,31 @@ sub tables_hashref { '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', '', '', '', '', diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm index 9b322093b..109343aa3 100644 --- a/FS/FS/agent.pm +++ b/FS/FS/agent.pm @@ -1,19 +1,18 @@ 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 @@ -177,6 +176,31 @@ sub agent_cust_main { qsearchs( 'cust_main', { 'custnum' => $self->agent_custnum } ); } +=item agent_currency + +Returns the FS::agent_currency objects (see L), 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 diff --git a/FS/FS/agent_currency.pm b/FS/FS/agent_currency.pm new file mode 100644 index 000000000..e387844bf --- /dev/null +++ b/FS/FS/agent_currency.pm @@ -0,0 +1,110 @@ +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) + +=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 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, L + +=cut + +1; + diff --git a/FS/FS/currency_exchange.pm b/FS/FS/currency_exchange.pm new file mode 100644 index 000000000..68832b62d --- /dev/null +++ b/FS/FS/currency_exchange.pm @@ -0,0 +1,116 @@ +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 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, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 605c84f95..67372ac64 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -25,6 +25,7 @@ use FS::part_pkg_link; 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; @@ -177,6 +178,9 @@ records will be inserted. If I is set to a hashref of options, appropriate FS::part_pkg_option records will be inserted. +If I 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 { @@ -251,6 +255,23 @@ 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; @@ -352,6 +373,9 @@ FS::pkg_svc record will be updated. If I is set to a hashref, the appropriate FS::part_pkg_option records will be replaced. +If I 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 { @@ -447,6 +471,34 @@ 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'} || {}; @@ -1191,6 +1243,33 @@ sub option { ''; } +=item part_pkg_currency [ CURRENCY ] + +Returns all currency options as FS::part_pkg_currency objects (see +L), 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). diff --git a/FS/FS/part_pkg_currency.pm b/FS/FS/part_pkg_currency.pm new file mode 100644 index 000000000..246abee8b --- /dev/null +++ b/FS/FS/part_pkg_currency.pm @@ -0,0 +1,139 @@ +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). + +=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 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, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index 68b4accd8..a86683d6b 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -699,3 +699,9 @@ FS/cable_device.pm 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 diff --git a/FS/t/agent_currency.t b/FS/t/agent_currency.t new file mode 100644 index 000000000..152e066b5 --- /dev/null +++ b/FS/t/agent_currency.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::agent_currency; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/currency_exchange.t b/FS/t/currency_exchange.t new file mode 100644 index 000000000..6f8ac1de0 --- /dev/null +++ b/FS/t/currency_exchange.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::currency_exchange; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/part_pkg_currency.t b/FS/t/part_pkg_currency.t new file mode 100644 index 000000000..b8654c7e3 --- /dev/null +++ b/FS/t/part_pkg_currency.t @@ -0,0 +1,5 @@ +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"; diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi index fc9ce5413..b9190ecb5 100755 --- a/httemplate/browse/agent.cgi +++ b/httemplate/browse/agent.cgi @@ -38,6 +38,10 @@ full offerings (via their type).

Ticketing % } +% if ( $conf->config('currencies') ) { + Currencies +% } + Payment Gateway Overrides Configuration Overrides @@ -361,19 +365,23 @@ Unused
Generate cards -% if ( $conf->config('ticket_system') ) { - +% if ( $conf->config('ticket_system') ) { -% if ( $agent->ticketing_queueid ) { - - Queue: <% $agent->ticketing_queueid %>: <% $agent->ticketing_queue %>
+% if ( $agent->ticketing_queueid ) { + Queue: <% $agent->ticketing_queueid %>: + <% $agent->ticketing_queue %> +
+% } + % } +% if ( $conf->config('currencies') ) { + + <% join('
', sort keys %{ $agent->agent_currency_hashref } ) %> % } - % foreach my $override ( diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi index 7960d7e38..50b3eba85 100644 --- a/httemplate/config/config.cgi +++ b/httemplate/config/config.cgi @@ -156,7 +156,9 @@ Setting <% $key %> % } % 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}++; diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi index b043d1efe..2eddd30a4 100755 --- a/httemplate/edit/agent.cgi +++ b/httemplate/edit/agent.cgi @@ -170,9 +170,30 @@ % }
+
+ +% if ( $conf->config('currencies') ) { + + <% mt('Currencies') |h %> + + + + +
+ <& /elements/checkboxes-table-name.html, + 'link_table' => 'agent_currency', + 'name_col' => 'currency', + 'names_list' => [ map [ $_, {label=>"$_: ".code2currency($_)} ], + $conf->config('currencies') + ], + &> +
+% }
+ + "> diff --git a/httemplate/edit/currency_exchange.html b/httemplate/edit/currency_exchange.html new file mode 100755 index 000000000..573ace5ee --- /dev/null +++ b/httemplate/edit/currency_exchange.html @@ -0,0 +1,73 @@ +<& /elements/header.html, 'Exchange rates' &> + +
+ +<& /elements/table-grid.html &> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; + + + From + Rate + To + + +%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; + + + + + <% $currency %>: <% code2currency($currency) %> + + + + " + VALUE = "<% $currency_exchange->rate %>" + SIZE = 14 + MAXLENGTH = 14 + > + + + + <% $to_currency %>: <% code2currency($to_currency) %> + + + +% } + + + +
+ +
+ +<& /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'); + + diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 3e6bd5bec..08408297b 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -282,6 +282,7 @@ Example: % #text and derivitives % 'size' => $f->{'size'}, % 'maxlength' => $f->{'maxlength'}, +% 'prefix' => $f->{'prefix'}, % 'postfix' => $f->{'postfix'}, % % #textarea diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index fadde354e..ef9bc2299 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -51,6 +51,12 @@ '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', @@ -118,6 +124,14 @@ 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', @@ -127,12 +141,19 @@ 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 @@ -460,6 +481,14 @@ my $error_callback = sub { $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( @@ -535,6 +564,12 @@ my $edit_callback = sub { $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( @@ -599,6 +634,12 @@ my $clone_callback = sub { $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( diff --git a/httemplate/edit/process/agent.cgi b/httemplate/edit/process/agent.cgi index 034c4cc50..554992958 100755 --- a/httemplate/edit/process/agent.cgi +++ b/httemplate/edit/process/agent.cgi @@ -5,6 +5,12 @@ '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, ) @@ -14,7 +20,9 @@ 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"; } diff --git a/httemplate/edit/process/currency_exchange.html b/httemplate/edit/process/currency_exchange.html new file mode 100644 index 000000000..1f6852299 --- /dev/null +++ b/httemplate/edit/process/currency_exchange.html @@ -0,0 +1,36 @@ +%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; +} + + diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index 932e33b1d..3b6562f13 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -115,6 +115,19 @@ my $args_callback = sub { 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 ### diff --git a/httemplate/elements/checkboxes-table-name.html b/httemplate/elements/checkboxes-table-name.html index 8ee2f7736..957d8efda 100644 --- a/httemplate/elements/checkboxes-table-name.html +++ b/httemplate/elements/checkboxes-table-name.html @@ -11,7 +11,7 @@ Example: 'name_col' => 'name_column', #or - 'name_callback' => sub { }, + #not yet 'name_callback' => sub { }, 'names_list' => [ 'value', 'other value', diff --git a/httemplate/elements/checkboxes.html b/httemplate/elements/checkboxes.html index 69ef18fb9..ad9d691b9 100644 --- a/httemplate/elements/checkboxes.html +++ b/httemplate/elements/checkboxes.html @@ -6,7 +6,7 @@ Example: # required - #? 'name_callback' => sub { }, + #not yet 'name_callback' => sub { }, 'names_list' => [ 'value', 'other value', diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index f784d2faf..53fccaf25 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -587,8 +587,10 @@ $config_billing{'Billing events'} = [ $fsurl.'browse/part_event.html', 'Billing 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') @@ -600,6 +602,12 @@ if ( $curuser->access_right('Configuration') ) { 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.' ]; } -- 2.11.0