From b7a2175dd9b386441f4ab66869d73083e5e8beb1 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Mon, 10 Jun 2013 23:15:03 -0700 Subject: [PATCH] multi-currency, RT#21565 --- FS/FS/Schema.pm | 38 ++++++++++++++++-------------- FS/FS/cust_bill_pkg.pm | 6 +++++ FS/FS/cust_main.pm | 2 +- FS/FS/cust_main/Billing.pm | 43 ++++++++++++++++++++++++---------- FS/FS/cust_pkg.pm | 24 +++++++++++++++---- FS/FS/part_pkg/currency_fixed.pm | 40 +++++++++++++++++++++++++------ FS/FS/part_pkg/flat.pm | 4 ++-- FS/FS/part_pkg/recur_Common.pm | 2 +- httemplate/edit/cust_main/billing.html | 17 ++++++++++++++ httemplate/search/cust_bill_pkg.cgi | 28 ++++++++++++++++++++++ httemplate/view/cust_main/billing.html | 7 ++++++ 11 files changed, 165 insertions(+), 46 deletions(-) diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index f1cc09c02..da3ddab99 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -768,24 +768,26 @@ sub tables_hashref { 'cust_bill_pkg' => { 'columns' => [ - 'billpkgnum', 'serial', '', '', '', '', - 'invnum', 'int', '', '', '', '', - 'pkgnum', 'int', '', '', '', '', - 'pkgpart_override', 'int', 'NULL', '', '', '', - 'setup', @money_type, '', '', - 'recur', @money_type, '', '', - #XXX a currency for a line item? or just one for the entire invoice - #'currency', 'char', 'NULL', 3, '', '', - 'sdate', @date_type, '', '', - 'edate', @date_type, '', '', - 'itemdesc', 'varchar', 'NULL', $char_d, '', '', - 'itemcomment', 'varchar', 'NULL', $char_d, '', '', - 'section', 'varchar', 'NULL', $char_d, '', '', - 'freq', 'varchar', 'NULL', $char_d, '', '', - 'quantity', 'int', 'NULL', '', '', '', - 'unitsetup', @money_typen, '', '', - 'unitrecur', @money_typen, '', '', - 'hidden', 'char', 'NULL', 1, '', '', + 'billpkgnum', 'serial', '', '', '', '', + 'invnum', 'int', '', '', '', '', + 'pkgnum', 'int', '', '', '', '', + 'pkgpart_override', 'int', 'NULL', '', '', '', + 'setup', @money_type, '', '', + 'unitsetup', @money_typen, '', '', + 'setup_billed_currency', 'char', 'NULL', 3, '', '', + 'setup_billed_amount', @money_typen, '', '', + 'recur', @money_type, '', '', + 'unitrecur', @money_typen, '', '', + 'recur_billed_currency', 'char', 'NULL', 3, '', '', + 'recur_billed_amount', @money_typen, '', '', + 'sdate', @date_type, '', '', + 'edate', @date_type, '', '', + 'itemdesc', 'varchar', 'NULL', $char_d, '', '', + 'itemcomment', 'varchar', 'NULL', $char_d, '', '', + 'section', 'varchar', 'NULL', $char_d, '', '', + 'freq', 'varchar', 'NULL', $char_d, '', '', + 'quantity', 'int', 'NULL', '', '', '', + 'hidden', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'billpkgnum', 'unique' => [], diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm index 0c8c0bbbf..572fe7973 100644 --- a/FS/FS/cust_bill_pkg.pm +++ b/FS/FS/cust_bill_pkg.pm @@ -434,7 +434,13 @@ sub check { || $self->ut_snumber('pkgnum') || $self->ut_number('invnum') || $self->ut_money('setup') + || $self->ut_moneyn('unitsetup') + || $self->ut_currencyn('setup_billed_currency') + || $self->ut_moneyn('setup_billed_amount') || $self->ut_money('recur') + || $self->ut_moneyn('unitrecur') + || $self->ut_currencyn('recur_billed_currency') + || $self->ut_moneyn('recur_billed_amount') || $self->ut_numbern('sdate') || $self->ut_numbern('edate') || $self->ut_textn('itemdesc') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 3fbee1594..f3122aaa2 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1757,7 +1757,7 @@ sub check { || $self->ut_flag('invoice_noemail') || $self->ut_flag('message_noemail') || $self->ut_enum('locale', [ '', FS::Locales->locales ]) - || $self->ut_currency('currency') + || $self->ut_currencyn('currency') ; my $company = $self->company; diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 814802b34..220f66a0c 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -951,6 +951,8 @@ sub _make_lines { my $unitsetup = 0; my @setup_discounts = (); my %setup_param = ( 'discounts' => \@setup_discounts ); + my $setup_billed_currency = ''; + my $setup_billed_amount = 0; if ( ! $options{recurring_only} and ! $options{cancel} and ( $options{'resetup'} @@ -977,7 +979,13 @@ sub _make_lines { return "$@ running calc_setup for $cust_pkg\n" if $@; - $unitsetup = $cust_pkg->part_pkg->unit_setup || $setup; #XXX uuh + $unitsetup = $cust_pkg->base_setup() + || $setup; #XXX uuh + + if ( $setup_param{'billed_currency'} ) { + $setup_billed_currency = delete $setup_param{'billed_currency'}; + $setup_billed_amount = delete $setup_param{'billed_amount'}; + } } $cust_pkg->setfield('setup', $time) @@ -997,6 +1005,8 @@ sub _make_lines { my $recur = 0; my $unitrecur = 0; my @recur_discounts = (); + my $recur_billed_currency = ''; + my $recur_billed_amount = 0; my $sdate; if ( ! $cust_pkg->start_date and ( ! $cust_pkg->susp || $cust_pkg->option('suspend_bill',1) @@ -1058,6 +1068,11 @@ sub _make_lines { #base_cancel??? $unitrecur = $cust_pkg->base_recur( \$sdate ) || $recur; #XXX uuh, better + if ( $param{'billed_currency'} ) { + $recur_billed_currency = delete $param{'billed_currency'}; + $recur_billed_amount = delete $param{'billed_amount'}; + } + if ( $increment_next_bill ) { my $next_bill; @@ -1173,16 +1188,20 @@ sub _make_lines { push @details, @cust_pkg_detail; my $cust_bill_pkg = new FS::cust_bill_pkg { - 'pkgnum' => $cust_pkg->pkgnum, - 'setup' => $setup, - 'unitsetup' => $unitsetup, - 'recur' => $recur, - 'unitrecur' => $unitrecur, - 'quantity' => $cust_pkg->quantity, - 'details' => \@details, - 'discounts' => [ @setup_discounts, @recur_discounts ], - 'hidden' => $part_pkg->hidden, - 'freq' => $part_pkg->freq, + 'pkgnum' => $cust_pkg->pkgnum, + 'setup' => $setup, + 'unitsetup' => $unitsetup, + 'setup_billed_currency' => $setup_billed_currency, + 'setup_billed_amount' => $setup_billed_amount, + 'recur' => $recur, + 'unitrecur' => $unitrecur, + 'recur_billed_currency' => $recur_billed_currency, + 'recur_billed_amount' => $recur_billed_amount, + 'quantity' => $cust_pkg->quantity, + 'details' => \@details, + 'discounts' => [ @setup_discounts, @recur_discounts ], + 'hidden' => $part_pkg->hidden, + 'freq' => $part_pkg->freq, }; if ( $part_pkg->option('prorate_defer_bill',1) @@ -1194,7 +1213,7 @@ sub _make_lines { $cust_bill_pkg->sdate( $hash{last_bill} ); $cust_bill_pkg->edate( $sdate - 86399 ); #60s*60m*24h-1 $cust_bill_pkg->edate( $time ) if $options{cancel}; - } else { #if ( $part_pkg->recur_temporality eq 'upcoming' ) { + } else { #if ( $part_pkg->recur_temporality eq 'upcoming' ) $cust_bill_pkg->sdate( $sdate ); $cust_bill_pkg->edate( $cust_pkg->bill ); #$cust_bill_pkg->edate( $time ) if $options{cancel}; diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 835738604..3d24ea5b2 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -2191,6 +2191,18 @@ sub calc_recur { $self->part_pkg->calc_recur($self, @_); } +=item base_setup + +Calls the I of the FS::part_pkg object associated with this billing +item. + +=cut + +sub base_setup { + my $self = shift; + $self->part_pkg->base_setup($self, @_); +} + =item base_recur Calls the I of the FS::part_pkg object associated with this billing @@ -2345,9 +2357,11 @@ sub num_cust_event { =item part_pkg_currency_option OPTIONNAME -Returns the option value for the given name and the currency of this customer -(see L). If this customer has no currency, returns -the regular option value for the given name (see L). +Returns a two item list consisting of the currency of this customer, if any, +and a value for the provided option. If the customer has a currency, the value +is the option value the given name and the currency (see +L). Otherwise, if the customer has no currency, is the +regular option value for the given name (see L). =cut @@ -2355,9 +2369,9 @@ sub part_pkg_currency_option { my( $self, $optionname ) = @_; my $part_pkg = $self->part_pkg; if ( my $currency = $self->cust_main->currency ) { - $part_pkg->part_pkg_currency_option($currency, $optionname); + ($currency, $part_pkg->part_pkg_currency_option($currency, $optionname) ); } else { - $part_pkg->option($optionname); + ('', $part_pkg->option($optionname) ); } } diff --git a/FS/FS/part_pkg/currency_fixed.pm b/FS/FS/part_pkg/currency_fixed.pm index bee6c5b4e..ce7145278 100644 --- a/FS/FS/part_pkg/currency_fixed.pm +++ b/FS/FS/part_pkg/currency_fixed.pm @@ -5,7 +5,8 @@ use base qw( FS::part_pkg::recur_Common ); use strict; use vars qw( %info ); -#use FS::Record qw(qsearch qsearchs); +use FS::Record qw(qsearchs); # qsearch qsearchs); +use FS::currency_exchange; %info = ( 'name' => 'Per-currency pricing from package definitions', @@ -26,7 +27,7 @@ use vars qw( %info ); }, 'fieldorder' => [qw( recur_method cutoff_day ), FS::part_pkg::prorate_Mixin::fieldorder, - )], + ], 'weight' => '59', ); @@ -37,19 +38,44 @@ sub price_info { $str; } -#some false laziness w/recur_Common, could have been better about it.. pry when -# we do discounting +sub base_setup { + my($self, $cust_pkg, $sdate, $details, $param ) = @_; + + $self->calc_currency_option('setup_fee', $cust_pkg, $sdate, $details, $param); +} + sub calc_setup { my($self, $cust_pkg, $sdate, $details, $param) = @_; return 0 if $self->prorate_setup($cust_pkg, $sdate); - sprintf('%.2f', $cust_pkg->part_pkg_currency_option('setup_fee') ); + $self->base_setup($cust_pkg, $sdate, $details, $param); +} + +use FS::Conf; +sub calc_currency_option { + my($self, $optionname, $cust_pkg, $sdate, $details, $param) = @_; + + my($currency, $amount) = $cust_pkg->part_pkg_currency_option($optionname); + return sprintf('%.2f', $amount ) unless $currency; + + $param->{'billed_currency'} = $currency; + $param->{'billed_amount'} = $amount; + + my $currency_exchange = qsearchs('currency_exchange', { + 'from_currency' => $currency, + 'to_currency' => ( FS::Conf->new->config('currency') || 'USD' ), + }) or die "No exchange rate from $currency\n"; + + #XXX do we want the rounding here to work differently? + #my $recognized_amount = + sprintf('%.2f', $amount * $currency_exchange->rate); } sub base_recur { - my( $self, $cust_pkg ) = @_; - sprintf('%.2f', $cust_pkg->part_pkg_currency_option('recur_fee') ); + my( $self, $cust_pkg, $sdate, $details, $param ) = @_; + $param ||= {}; + $self->calc_currency_option('recur_fee', $cust_pkg, $sdate, $details, $param); } sub can_discount { 0; } #can't discount yet (percentage would work, but amount?) diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index 22eb69815..9737a94c0 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -122,7 +122,7 @@ sub calc_setup { my $quantity = $cust_pkg->quantity || 1; - my $charge = $quantity * $self->unit_setup($cust_pkg, $sdate, $details); + my $charge = $quantity * $self->base_setup($cust_pkg, $sdate, $details); my $discount = 0; if ( $charge > 0 ) { @@ -134,7 +134,7 @@ sub calc_setup { sprintf('%.2f', $charge - $discount); } -sub unit_setup { +sub base_setup { my($self, $cust_pkg, $sdate, $details ) = @_; $self->option('setup_fee') || 0; diff --git a/FS/FS/part_pkg/recur_Common.pm b/FS/FS/part_pkg/recur_Common.pm index 03d5c2cb2..ebf8869f6 100644 --- a/FS/FS/part_pkg/recur_Common.pm +++ b/FS/FS/part_pkg/recur_Common.pm @@ -61,7 +61,7 @@ sub calc_recur_Common { my $recur_method = $self->option('recur_method', 1) || 'anniversary'; my @cutoff_day = $self->cutoff_day($cust_pkg); - $charges = $self->base_recur($cust_pkg); + $charges = $self->base_recur($cust_pkg, $sdate, $details, $param); $charges += $param->{'override_charges'} if $param->{'override_charges'}; if ( $recur_method eq 'prorate' ) { diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html index 5a66f0a60..da5f0f27f 100644 --- a/httemplate/edit/cust_main/billing.html +++ b/httemplate/edit/cust_main/billing.html @@ -615,6 +615,23 @@ function toggle(obj) { % } +%my @currencies = $conf->config('currencies'); +%if ( scalar(@currencies) ) { +% unshift @currencies, ''; #default +% my %currency_labels = map { $_ => "$_: ". code2currency($_) } @currencies; +% $currency_labels{''} = +% 'Default: '. code2currency( $conf->config('currency') || 'USD' ); + + <& /elements/tr-select.html, + 'label' => emt('Invoicing currency'), + 'field' => 'currency', + 'options' => \@currencies, + 'labels' => \%currency_labels, + 'curr_value' => $cust_main->currency, + &> +% } + + %my @available_locales = $conf->config('available-locales'); %if ( scalar(@available_locales) ) { % push @available_locales, '' diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi index 183051170..b2ff45b09 100644 --- a/httemplate/search/cust_bill_pkg.cgi +++ b/httemplate/search/cust_bill_pkg.cgi @@ -10,6 +10,7 @@ emt('Description'), @post_desc_header, @peritem_desc, + @currency_desc, emt('Invoice'), emt('Date'), emt('Paid'), @@ -32,6 +33,7 @@ #strikethrough or "N/A ($amount)" or something these when # they're not applicable to pkg_tax search @peritem_sub, + @currency_sub, 'invnum', sub { time2str('%b %d %Y', shift->_date ) }, sub { sprintf($money_char.'%.2f', shift->get('pay_amount')) }, @@ -44,6 +46,7 @@ '', @post_desc_null, @peritem, + @currency, 'invnum', '_date', #'pay_amount', @@ -55,6 +58,7 @@ '', @post_desc_null, @peritem_null, + @currency_null, $ilink, $ilink, $pay_link, @@ -68,6 +72,7 @@ 'rl'. $post_desc_align. $peritem_align. + $currency_align. 'rcrr'. FS::UI::Web::cust_aligns(), 'color' => [ @@ -76,6 +81,7 @@ '', @post_desc_null, @peritem_null, + @currency_null, '', '', '', @@ -88,6 +94,7 @@ '', @post_desc_null, @peritem_null, + @currency_null, '', '', '', @@ -196,6 +203,23 @@ my @total_desc = ( $money_char.'%.2f total' ); # sprintf strings my @peritem = ( 'setup', 'recur' ); my @peritem_desc = ( 'Setup charge', 'Recurring charge' ); +my @currency_desc = (); +my @currency_sub = (); +my @currency = (); +if ( $conf->config('currencies') ) { + @currency_desc = ( 'Setup billed', 'Recurring billed' ); + @currency_sub = ( + map { + my $what = $_; + sub { my $currency = $_[0]->get($what.'_billed_currency'); + $currency. ' '. currency_symbol($currency, SYM_HTML). + $_[0]->get($what.'_billed_amount'); + }; + } qw( setup recur ) + ); + @currency = ( 'setup_billed_amount', 'recur_billed_amount' ); #for sorting +} + my @pkgnum_header = (); my @pkgnum = (); my @pkgnum_null; @@ -672,6 +696,10 @@ my @peritem_sub = map { my @peritem_null = map { '' } @peritem; # placeholders my $peritem_align = 'r' x scalar(@peritem); +@currency_desc = map {emt($_)} @currency_desc; +my @currency_null = map { '' } @currency; # placeholders +my $currency_align = 'r' x scalar(@currency); + my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html index b863a734b..e286305f4 100644 --- a/httemplate/view/cust_main/billing.html +++ b/httemplate/view/cust_main/billing.html @@ -304,6 +304,13 @@ % } +% if ( $cust_main->currency ) { + + <% mt('Invoicing currency') |h %> + <% $cust_main->currency. ': '. code2currency($cust_main->currency) %> + +% } + % if ( $cust_main->locale ) { % my %locale_info = FS::Locales->locale_info($cust_main->locale); -- 2.11.0