cust_event
cust_credit_bill
cust_bill_pay
- cust_bill_pay
cust_credit_bill
cust_pay_batch
cust_bill_pay_batch
notice_name - overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+locale - override customer's locale
+
=cut
#what's with all the sprintf('%10.2f')'s in here? will it cause any
# (alignment in text invoice?) problems to change them all to '%.2f' ?
-# yes: fixed width (dot matrix) text printing will be borked
+# yes: fixed width/plain text printing will be borked
sub print_generic {
my( $self, %params ) = @_;
my $conf = $self->conf;
);
#localization
- my $lh = FS::L10N->get_handle($cust_main->locale);
+ my $lh = FS::L10N->get_handle( $params{'locale'} || $cust_main->locale );
$invoice_data{'emt'} = sub { &$escape_function($self->mt(@_)) };
my %info = FS::Locales->locale_info($cust_main->locale || 'en_US');
# eval to avoid death for unimplemented languages
my $previous_section = { 'description' => $self->mt('Previous Charges'),
'subtotal' => $other_money_char.
sprintf('%.2f', $pr_total),
- 'summarized' => $summarypage ? 'Y' : '',
+ 'summarized' => '', #why? $summarypage ? 'Y' : '',
};
$previous_section->{posttotal} = '0 / 30 / 60 / 90 days overdue '.
join(' / ', map { $cust_main->balance_date_range(@$_) }
my $taxtotal = 0;
my $tax_section = { 'description' => $self->mt('Taxes, Surcharges, and Fees'),
'subtotal' => $taxtotal, # adjusted below
- 'summarized' => $summarypage ? 'Y' : '',
};
my $tax_weight = _pkg_category($tax_section->{description})
? _pkg_category($tax_section->{description})->weight
: 0;
- $tax_section->{'summarized'} = $summarypage && !$tax_weight ? 'Y' : '';
+ $tax_section->{'summarized'} = ''; #why? $summarypage && !$tax_weight ? 'Y' : '';
$tax_section->{'sort_weight'} = $tax_weight;
my $adjust_section = { 'description' =>
$self->mt('Credits, Payments, and Adjustments'),
'subtotal' => 0, # adjusted below
- 'summarized' => $summarypage ? 'Y' : '',
};
my $adjust_weight = _pkg_category($adjust_section->{description})
? _pkg_category($adjust_section->{description})->weight
: 0;
- $adjust_section->{'summarized'} = $summarypage && !$adjust_weight ? 'Y' : '';
+ $adjust_section->{'summarized'} = ''; #why? $summarypage && !$adjust_weight ? 'Y' : '';
$adjust_section->{'sort_weight'} = $adjust_weight;
my $unsquelched = $params{unsquelch_cdr} || $cust_main->squelch_cdr ne 'Y';
$options{'section'} = $section if $multisection;
$options{'format'} = $format;
$options{'escape_function'} = $escape_function;
- $options{'format_function'} = sub { () } unless $unsquelched;
+ $options{'no_usage'} = 1 unless $unsquelched;
$options{'unsquelched'} = $unsquelched;
$options{'summary_page'} = $summarypage;
$options{'skip_usage'} =
escape_function: the function used to escape strings.
+DEPRECATED? (expensive, mostly unused?)
format_function: the function used to format CDRs.
section: a hashref containing 'description'; if this is present,
my $format = $opt{format} || '';
my $escape_function = $opt{escape_function} || sub { shift };
my $format_function = $opt{format_function} || '';
+ my $no_usage = $opt{no_usage} || '';
my $unsquelched = $opt{unsquelched} || ''; #unused
my $section = $opt{section}->{description} if $opt{section};
my $summary_page = $opt{summary_page} || ''; #unused
my %details_opt = ( 'format' => $format,
'escape_function' => $escape_function,
'format_function' => $format_function,
+ 'no_usage' => $opt{'no_usage'},
);
if ( $cust_bill_pkg->pkgnum > 0 ) {
#instead of omitting details entirely in this case (unwanted side
# effects), just omit CDRs
- $details_opt{'format_function'} = sub { () }
+ $details_opt{'no_usage'} = 1
if $type && $type eq 'R';
push @d, $cust_bill_pkg->details(%details_opt);
sub _items_discounts_avail {
my $self = shift;
- my %total;
- my $pkgnums = 0;
- my $pkgnums_times_discounts = 0;
- # tricky, because packages may not all be eligible for the same discounts
- foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
- my $cust_pkg = $cust_bill_pkg->cust_pkg or next;
- my $part_pkg = $cust_pkg->part_pkg or next;
- # for simplicity, skip all this if the customer already has a term discount
- return () if $cust_pkg->cust_pkg_discount_active;
-
- $pkgnums++;
- next if $part_pkg->freq ne '1';
-
- foreach my $discount (
- map { $_->discount } $part_pkg->part_pkg_discount
- ) {
-
- $total{$discount->discountnum} ||=
- {
- discount => $discount,
- pkgnums => [],
- base_current => 0,
- base_permonth => 0,
- setup_include => 0,
- setup_exclude => 0,
- };
- my $hash = $total{$discount->discountnum};
- $hash->{discount} = $discount;
- $hash->{thismonth} += $cust_bill_pkg->recur || 0;
- $hash->{setup} += $cust_bill_pkg->setup || 0;
- $hash->{base_permonth} += $part_pkg->base_recur_permonth;
-
- # and make a list of pkgnums
- push @{ $hash->{pkgnums} }, $cust_pkg->pkgnum;
- $pkgnums_times_discounts++;
+ my %terms;
+ my $list_pkgnums = 0; # if any packages are not eligible for all discounts
+
+ my ($previous_balance) = $self->previous;
+
+ foreach (qsearch('discount',{ 'months' => { op => '>', value => 1} })) {
+ $terms{$_->months} = {
+ pkgnums => [],
+ base => $previous_balance || 0, # pre-discount sum of charges
+ discounted => $previous_balance || 0, # post-discount sum
+ list_pkgnums => 0, # whether any packages are not discounted
}
}
+ foreach my $months (keys %terms) {
+ my $hash = $terms{$months};
- # Test for the simple case where all packages on the invoice
- # are eligible for the same set of discounts. If not, we need
- # to list eligibility in the ext_description.
- my $list_pkgnums = ( $pkgnums_times_discounts != $pkgnums * keys(%total) );
-
- foreach my $hash (values %total) {
- my $discount = $hash->{discount};
- my ($amount, $term_total, $percent, $permonth);
- my $months = $discount->months;
- $hash->{months} = $months;
-
- if ( $discount->percent ) {
-
- # per discount_Mixin, percent discounts are calculated on the base
- # recurring fee, not the prorated fee.
- $percent = $discount->percent;
- $amount = sprintf('%.2f', 0.01 * $percent * $hash->{base_permonth});
- # percent discounts apply to setup fee
- if ( $discount->setup ) {
- $hash->{setup} *= (1 - 0.01*$percent);
- }
+ # tricky, because packages may not all be eligible for the same discounts
+ foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
+ my $cust_pkg = $cust_bill_pkg->cust_pkg or next;
+ my $part_pkg = $cust_pkg->part_pkg or next;
+ my $freq = $part_pkg->freq;
+ my $setup = $cust_bill_pkg->setup || 0;
+ my $recur = $cust_bill_pkg->recur || 0;
- }
- elsif ( $discount->amount > 0 ) {
+ if ( $freq eq '1' ) { #monthly
+ my $permonth = $part_pkg->base_recur_permonth || 0;
+
+ my ($discount) = grep { $_->months == $months }
+ map { $_->discount } $part_pkg->part_pkg_discount;
+
+ $hash->{base} += $setup + $recur + ($months - 1) * $permonth;
- # amount discounts are amount * number of packages
- $amount = $discount->amount * scalar(@{ $hash->{pkgnums} });
- $percent = sprintf('%.0f', 100 * $amount / $hash->{base_permonth});
+ if ( $discount ) {
+
+ my $discountable;
+ if ( $discount->setup ) {
+ $discountable += $setup;
+ }
+ else {
+ $hash->{discounted} += $setup;
+ }
- # flat discounts are applied to setup and recur together
- if ( $discount->setup ) {
- $hash->{thismonth} += $hash->{setup};
- $hash->{setup} = 0;
+ if ( $discount->percent ) {
+ $discountable += $months * $permonth;
+ $discountable -= ($discountable * $discount->percent / 100);
+ $discountable -= ($permonth - $recur); # correct for prorate
+ $hash->{discounted} += $discountable;
+ }
+ else {
+ $discountable += $recur;
+ $discountable -= $discount->amount * $recur/$permonth;
+
+ $discountable += ($months - 1) * max($permonth - $discount->amount,0);
+ }
+
+ $hash->{discounted} += $discountable;
+ push @{ $hash->{pkgnums} }, $cust_pkg->pkgnum;
+ }
+ else { #no discount
+ $hash->{discounted} += $setup + $recur + ($months - 1) * $permonth;
+ $hash->{list_pkgnums} = 1;
+ }
+ } #if $freq eq '1'
+ else { # all non-monthly packages: include current charges only
+ $hash->{discounted} += $setup + $recur;
+ $hash->{base} += $setup + $recur;
+ $hash->{list_pkgnums} = 1;
}
+ } #foreach $cust_bill_pkg
- }
+ # don't show this line if no packages have discounts at this term
+ # or if there are no new charges to apply the discount to
+ delete $terms{$months} if $hash->{base} == $hash->{discounted}
+ or $hash->{base} == 0;
+
+ }
- $permonth = max( $hash->{base_permonth} - $amount, 0);
- $term_total = max( $hash->{thismonth} - $amount , 0 ) # this month
- + $permonth * ($months - 1) # rest of the term
- + $hash->{setup}; # setup fee
+ $list_pkgnums = grep { $_->{list_pkgnums} > 0 } values %terms;
+
+ foreach my $months (keys %terms) {
+ my $hash = $terms{$months};
+ my $term_total = sprintf('%.2f', $hash->{discounted});
+ # possibly shouldn't include previous balance in these?
+ my $percent = sprintf('%.0f', 100 * (1 - $term_total / $hash->{base}) );
+ my $permonth = sprintf('%.2f', $term_total / $months);
$hash->{description} = $self->mt('Save [_1]% by paying for [_2] months',
- $percent, $months,
+ $percent, $months
);
$hash->{amount} = $self->mt('[_1] ([_2] per month)',
- sprintf('%.2f',$term_total), #no money_char to accommodate template quirk
- $money_char.sprintf('%.2f',$permonth) );
+ $term_total, $money_char.$permonth
+ );
my @detail;
if ( $list_pkgnums ) {
- push @detail, $self->mt('for item'). ' '.
+ push @detail, $self->mt('discount on item'). ' '.
join(', ', map { "#$_" } @{ $hash->{pkgnums} });
}
- if ( !$discount->setup and $hash->{setup} ) {
- push @detail, $self->mt('excluding setup fees');
- }
$hash->{ext_description} = join ', ', @detail;
}
- sort { -( $a->{months} <=> $b->{months} ) } values(%total);
+ map { $terms{$_} } sort {$b <=> $a} keys %terms;
}
=item call_details [ OPTION => VALUE ... ]