+=item set_exemptions TAXOBJECT, OPTIONS
+
+Sets up tax exemptions. TAXOBJECT is the L<FS::cust_main_county> or
+L<FS::tax_rate> record for the tax.
+
+This will deal with the following cases:
+
+=over 4
+
+=item Fully exempt customers (cust_main.tax flag) or customer classes
+(cust_class.tax).
+
+=item Customers exempt from specific named taxes (cust_main_exemption
+records).
+
+=item Taxes that don't apply to setup or recurring fees
+(cust_main_county.setuptax and recurtax, tax_rate.setuptax and recurtax).
+
+=item Packages that are marked as tax-exempt (part_pkg.setuptax,
+part_pkg.recurtax).
+
+=item Fees that aren't marked as taxable (part_fee.taxable).
+
+=back
+
+It does NOT deal with monthly tax exemptions, which need more context
+than this humble little method cares to deal with.
+
+OPTIONS should include "custnum" => the customer number if this tax line
+hasn't been inserted (which it probably hasn't).
+
+Returns a list of exemption objects, which will also be attached to the
+line item as the 'cust_tax_exempt_pkg' pseudo-field. Inserting the line
+item will insert these records as well.
+
+=cut
+
+sub set_exemptions {
+ my $self = shift;
+ my $tax = shift;
+ my %opt = @_;
+
+ my $part_pkg = $self->part_pkg;
+ my $part_fee = $self->part_fee;
+
+ my $cust_main;
+ my $custnum = $opt{custnum};
+ $custnum ||= $self->cust_bill->custnum if $self->cust_bill;
+
+ $cust_main = FS::cust_main->by_key( $custnum )
+ or die "set_exemptions can't identify customer (pass custnum option)\n";
+
+ my @new_exemptions;
+ my $taxable_charged = $self->setup + $self->recur;
+ return unless $taxable_charged > 0;
+
+ ### Fully exempt customer ###
+ my $exempt_cust;
+ my $conf = FS::Conf->new;
+ if ( $conf->exists('cust_class-tax_exempt') ) {
+ my $cust_class = $cust_main->cust_class;
+ $exempt_cust = $cust_class->tax if $cust_class;
+ } else {
+ $exempt_cust = $cust_main->tax;
+ }
+
+ ### Exemption from named tax ###
+ my $exempt_cust_taxname;
+ if ( !$exempt_cust and $tax->taxname ) {
+ $exempt_cust_taxname = $cust_main->tax_exemption($tax->taxname);
+ }
+
+ if ( $exempt_cust ) {
+
+ push @new_exemptions, FS::cust_tax_exempt_pkg->new({
+ amount => $taxable_charged,
+ exempt_cust => 'Y',
+ });
+ $taxable_charged = 0;
+
+ } elsif ( $exempt_cust_taxname ) {
+
+ push @new_exemptions, FS::cust_tax_exempt_pkg->new({
+ amount => $taxable_charged,
+ exempt_cust_taxname => 'Y',
+ });
+ $taxable_charged = 0;
+
+ }
+
+ my $exempt_setup = ( ($part_fee and not $part_fee->taxable)
+ or ($part_pkg and $part_pkg->setuptax)
+ or $tax->setuptax );
+
+ if ( $exempt_setup
+ and $self->setup > 0
+ and $taxable_charged > 0 ) {
+
+ push @new_exemptions, FS::cust_tax_exempt_pkg->new({
+ amount => $self->setup,
+ exempt_setup => 'Y'
+ });
+ $taxable_charged -= $self->setup;
+
+ }
+
+ my $exempt_recur = ( ($part_fee and not $part_fee->taxable)
+ or ($part_pkg and $part_pkg->recurtax)
+ or $tax->recurtax );
+
+ if ( $exempt_recur
+ and $self->recur > 0
+ and $taxable_charged > 0 ) {
+
+ push @new_exemptions, FS::cust_tax_exempt_pkg->new({
+ amount => $self->recur,
+ exempt_recur => 'Y'
+ });
+ $taxable_charged -= $self->recur;
+
+ }
+
+ foreach (@new_exemptions) {
+ $_->set('taxnum', $tax->taxnum);
+ $_->set('taxtype', ref($tax));
+ }
+
+ push @{ $self->cust_tax_exempt_pkg }, @new_exemptions;
+ return @new_exemptions;
+
+}
+