use Carp;
use Exporter;
use Scalar::Util qw( blessed );
-use Time::Local qw(timelocal timelocal_nocheck);
+use Time::Local qw(timelocal);
use Data::Dumper;
use Tie::IxHash;
use Digest::MD5 qw(md5_base64);
use FS::cust_bill;
use FS::cust_bill_pkg;
use FS::cust_bill_pkg_display;
+use FS::cust_bill_pkg_tax_location;
use FS::cust_pay;
use FS::cust_pay_pending;
use FS::cust_pay_void;
use FS::cust_refund;
use FS::part_referral;
use FS::cust_main_county;
+use FS::cust_location;
use FS::tax_rate;
use FS::cust_tax_location;
use FS::part_pkg_taxrate;
=over 4
-=item custnum - primary key (assigned automatically for new customers)
+=item custnum
-=item agentnum - agent (see L<FS::agent>)
+Primary key (assigned automatically for new customers)
-=item refnum - Advertising source (see L<FS::part_referral>)
+=item agentnum
+
+Agent (see L<FS::agent>)
+
+=item refnum
+
+Advertising source (see L<FS::part_referral>)
+
+=item first
+
+First name
+
+=item last
-=item first - name
+Last name
-=item last - name
+=item ss
-=item ss - social security number (optional)
+Cocial security number (optional)
-=item company - (optional)
+=item company
+
+(optional)
=item address1
-=item address2 - (optional)
+=item address2
+
+(optional)
=item city
-=item county - (optional, see L<FS::cust_main_county>)
+=item county
+
+(optional, see L<FS::cust_main_county>)
+
+=item state
-=item state - (see L<FS::cust_main_county>)
+(see L<FS::cust_main_county>)
=item zip
-=item country - (see L<FS::cust_main_county>)
+=item country
+
+(see L<FS::cust_main_county>)
+
+=item daytime
+
+phone (optional)
-=item daytime - phone (optional)
+=item night
-=item night - phone (optional)
+phone (optional)
-=item fax - phone (optional)
+=item fax
-=item ship_first - name
+phone (optional)
-=item ship_last - name
+=item ship_first
-=item ship_company - (optional)
+Shipping first name
+
+=item ship_last
+
+Shipping last name
+
+=item ship_company
+
+(optional)
=item ship_address1
-=item ship_address2 - (optional)
+=item ship_address2
+
+(optional)
=item ship_city
-=item ship_county - (optional, see L<FS::cust_main_county>)
+=item ship_county
+
+(optional, see L<FS::cust_main_county>)
+
+=item ship_state
-=item ship_state - (see L<FS::cust_main_county>)
+(see L<FS::cust_main_county>)
=item ship_zip
-=item ship_country - (see L<FS::cust_main_county>)
+=item ship_country
-=item ship_daytime - phone (optional)
+(see L<FS::cust_main_county>)
-=item ship_night - phone (optional)
+=item ship_daytime
+
+phone (optional)
+
+=item ship_night
+
+phone (optional)
+
+=item ship_fax
+
+phone (optional)
+
+=item payby
-=item ship_fax - phone (optional)
+Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
-=item payby - Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
+=item payinfo
-=item payinfo - Payment Information (See L<FS::payinfo_Mixin> for data format)
+Payment Information (See L<FS::payinfo_Mixin> for data format)
-=item paymask - Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
+=item paymask
+
+Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
=item paycvv
Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card
-=item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
+=item paydate
+
+Expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
+
+=item paystart_month
+
+Start date month (maestro/solo cards only)
+
+=item paystart_year
+
+Start date year (maestro/solo cards only)
+
+=item payissue
+
+Issue number (maestro/solo cards only)
+
+=item payname
+
+Name on card or billing name
+
+=item payip
+
+IP address from which payment information was received
+
+=item tax
+
+Tax exempt, empty or `Y'
-=item paystart_month - start date month (maestro/solo cards only)
+=item otaker
-=item paystart_year - start date year (maestro/solo cards only)
+Order taker (assigned automatically, see L<FS::UID>)
-=item payissue - issue number (maestro/solo cards only)
+=item comments
-=item payname - name on card or billing name
+Comments (optional)
-=item payip - IP address from which payment information was received
+=item referral_custnum
-=item tax - tax exempt, empty or `Y'
+Referring customer number
-=item otaker - order taker (assigned automatically, see L<FS::UID>)
+=item spool_cdr
-=item comments - comments (optional)
+Enable individual CDR spooling, empty or `Y'
-=item referral_custnum - referring customer number
+=item dundate
-=item spool_cdr - Enable individual CDR spooling, empty or `Y'
+A suggestion to events (see L<FS::part_bill_event">) to delay until this unix timestamp
-=item dundate - a suggestion to events (see L<FS::part_bill_event">) to delay until this unix timestamp
+=item squelch_cdr
-=item squelch_cdr - Discourage individual CDR printing, empty or `Y'
+Discourage individual CDR printing, empty or `Y'
=back
}
+=item order_pkg HASHREF | OPTION => VALUE ...
+
+Orders a single package.
+
+Options may be passed as a list of key/value pairs or as a hash reference.
+Options are:
+
+=over 4
+
+=item cust_pkg
+
+FS::cust_pkg object
+
+=item cust_location
+
+Optional FS::cust_location object
+
+=item svcs
+
+Optional arryaref of FS::svc_* service objects.
+
+=item depend_jobnum
+
+If this option is set to a job queue jobnum (see L<FS::queue>), all provisioning
+jobs will have a dependancy on the supplied job (they will not run until the
+specific job completes). This can be used to defer provisioning until some
+action completes (such as running the customer's credit card successfully).
+
+=back
+
+=cut
+
+sub order_pkg {
+ my $self = shift;
+ my $opt = ref($_[0]) ? shift : { @_ };
+
+ warn "$me order_pkg called with options ".
+ join(', ', map { "$_: $opt->{$_}" } keys %$opt ). "\n"
+ if $DEBUG;
+
+ my $cust_pkg = $opt->{'cust_pkg'};
+ my $seconds = $opt->{'seconds'};
+ my $svcs = $opt->{'svcs'} || [];
+
+ my %svc_options = ();
+ $svc_options{'depend_jobnum'} = $opt->{'depend_jobnum'}
+ if exists($opt->{'depend_jobnum'}) && $opt->{'depend_jobnum'};
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ if ( $opt->{'cust_location'} &&
+ ( ! $cust_pkg->locationnum || $cust_pkg->locationnum == -1 ) ) {
+ my $error = $opt->{'cust_location'}->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting cust_location (transaction rolled back): $error";
+ }
+ $cust_pkg->locationnum($opt->{'cust_location'}->locationnum);
+ }
+
+ $cust_pkg->custnum( $self->custnum );
+
+ my $error = $cust_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting cust_pkg (transaction rolled back): $error";
+ }
+
+ foreach my $svc_something ( @{ $opt->{'svcs'} } ) {
+ if ( $svc_something->svcnum ) {
+ my $old_cust_svc = $svc_something->cust_svc;
+ my $new_cust_svc = new FS::cust_svc { $old_cust_svc->hash };
+ $new_cust_svc->pkgnum( $cust_pkg->pkgnum);
+ $error = $new_cust_svc->replace($old_cust_svc);
+ } else {
+ $svc_something->pkgnum( $cust_pkg->pkgnum );
+ if ( $seconds && $$seconds && $svc_something->isa('FS::svc_acct') ) {
+ $svc_something->seconds( $svc_something->seconds + $$seconds );
+ $$seconds = 0;
+ }
+ $error = $svc_something->insert(%svc_options);
+ }
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting svc_ (transaction rolled back): $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+
+}
+
=item order_pkgs HASHREF, [ SECONDSREF, [ , OPTION => VALUE ... ] ]
-Like the insert method on an existing record, this method orders a package
-and included services atomicaly. Pass a Tie::RefHash data structure to this
-method containing FS::cust_pkg and FS::svc_I<tablename> objects. There should
-be a better explanation of this, but until then, here's an example:
+Like the insert method on an existing record, this method orders multiple
+packages and included services atomicaly. Pass a Tie::RefHash data structure
+to this method containing FS::cust_pkg and FS::svc_I<tablename> objects.
+There should be a better explanation of this, but until then, here's an
+example:
use Tie::RefHash;
tie %hash, 'Tie::RefHash'; #this part is important
my $cust_pkgs = shift;
my $seconds = shift;
my %options = @_;
- my %svc_options = ();
- $svc_options{'depend_jobnum'} = $options{'depend_jobnum'}
- if exists $options{'depend_jobnum'};
+
warn "$me order_pkgs called with options ".
join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
if $DEBUG;
local $FS::svc_Common::noexport_hack = 1 if $options{'noexport'};
foreach my $cust_pkg ( keys %$cust_pkgs ) {
- $cust_pkg->custnum( $self->custnum );
- my $error = $cust_pkg->insert;
+
+ my $error = $self->order_pkg( 'cust_pkg' => $cust_pkg,
+ 'svcs' => $cust_pkgs->{$cust_pkg},
+ 'seconds' => $seconds,
+ 'depend_jobnum' => $options{'depend_jobnum'},
+ );
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
- return "inserting cust_pkg (transaction rolled back): $error";
- }
- foreach my $svc_something ( @{$cust_pkgs->{$cust_pkg}} ) {
- if ( $svc_something->svcnum ) {
- my $old_cust_svc = $svc_something->cust_svc;
- my $new_cust_svc = new FS::cust_svc { $old_cust_svc->hash };
- $new_cust_svc->pkgnum( $cust_pkg->pkgnum);
- $error = $new_cust_svc->replace($old_cust_svc);
- } else {
- $svc_something->pkgnum( $cust_pkg->pkgnum );
- if ( $seconds && $$seconds && $svc_something->isa('FS::svc_acct') ) {
- $svc_something->seconds( $svc_something->seconds + $$seconds );
- $$seconds = 0;
- }
- $error = $svc_something->insert(%svc_options);
- }
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- #return "inserting svc_ (transaction rolled back): $error";
- return $error;
- }
+ return $error;
}
+
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
shift->all_pkgs(@_);
}
+=item cust_location
+
+Returns all locations (see L<FS::cust_location>) for this customer.
+
+=cut
+
+sub cust_location {
+ my $self = shift;
+ qsearch('cust_location', { 'custnum' => $self->custnum } );
+}
+
=item ncancelled_pkgs
Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
if $DEBUG;
my $time = $options{'time'} || time;
+ my $invoice_time = $options{'invoice_time'} || $time;
#put below somehow?
local $SIG{HUP} = 'IGNORE';
###
my( $total_setup, $total_recur, $postal_charge ) = ( 0, 0, 0 );
- my %tax;
my %taxlisthash;
- my %taxname;
my @precommit_hooks = ();
my @cust_pkgs = qsearch('cust_pkg', { 'custnum' => $self->custnum } );
}
warn "having a look at the taxes we found...\n" if $DEBUG > 2;
+
+ # keys are tax names (as printed on invoices / itemdesc )
+ # values are listrefs of taxlisthash keys (internal identifiers)
+ my %taxname = ();
+
+ # keys are taxlisthash keys (internal identifiers)
+ # values are (cumulative) amounts
+ my %tax = ();
+
+ # keys are taxlisthash keys (internal identifiers)
+ # values are listrefs of cust_bill_pkg_tax_location hashrefs
+ my %tax_location = ();
+
foreach my $tax ( keys %taxlisthash ) {
my $tax_object = shift @{ $taxlisthash{$tax} };
warn "found ". $tax_object->taxname. " as $tax\n" if $DEBUG > 2;
- my $listref_or_error = $tax_object->taxline( @{ $taxlisthash{$tax} } );
- unless (ref($listref_or_error)) {
+ my $hashref_or_error =
+ $tax_object->taxline( $taxlisthash{$tax},
+ 'custnum' => $self->custnum,
+ 'invoice_time' => $invoice_time
+ );
+ unless ( ref($hashref_or_error) ) {
$dbh->rollback if $oldAutoCommit;
- return $listref_or_error;
+ return $hashref_or_error;
}
unshift @{ $taxlisthash{$tax} }, $tax_object;
- warn "adding ". $listref_or_error->[1].
- " as ". $listref_or_error->[0]. "\n"
- if $DEBUG > 2;
- $tax{ $tax } += $listref_or_error->[1];
- if ( $taxname{ $listref_or_error->[0] } ) {
- push @{ $taxname{ $listref_or_error->[0] } }, $tax;
- }else{
- $taxname{ $listref_or_error->[0] } = [ $tax ];
+ my $name = $hashref_or_error->{'name'};
+ my $amount = $hashref_or_error->{'amount'};
+
+ #warn "adding $amount as $name\n";
+ $taxname{ $name } ||= [];
+ push @{ $taxname{ $name } }, $tax;
+
+ $tax{ $tax } += $amount;
+
+ $tax_location{ $tax } ||= [];
+ if ( $tax_object->get('pkgnum') || $tax_object->get('locationnum') ) {
+ push @{ $tax_location{ $tax } },
+ {
+ 'taxnum' => $tax_object->taxnum,
+ 'taxtype' => ref($tax_object),
+ 'pkgnum' => $tax_object->get('pkgnum'),
+ 'locationnum' => $tax_object->get('locationnum'),
+ 'amount' => sprintf('%.2f', $amount ),
+ };
+ }
+
+ }
+
+ #move the cust_tax_exempt_pkg records to the cust_bill_pkgs we will commit
+ my %packagemap = map { $_->pkgnum => $_ } @cust_bill_pkg;
+ foreach my $tax ( keys %taxlisthash ) {
+ foreach ( @{ $taxlisthash{$tax} }[1 ... scalar(@{ $taxlisthash{$tax} })] ) {
+ next unless ref($_) eq 'FS::cust_bill_pkg'; # shouldn't happen
+
+ push @{ $packagemap{$_->pkgnum}->_cust_tax_exempt_pkg },
+ splice( @{ $_->_cust_tax_exempt_pkg } );
}
-
}
#some taxes are taxed
my $tax_object = shift @{ $totlisthash{$tax} };
warn "found previously found taxed tax ". $tax_object->taxname. "\n"
if $DEBUG > 2;
- my $listref_or_error = $tax_object->taxline( @{ $totlisthash{$tax} } );
+ my $listref_or_error =
+ $tax_object->taxline( $totlisthash{$tax},
+ 'custnum' => $self->custnum,
+ 'invoice_time' => $invoice_time
+ );
unless (ref($listref_or_error)) {
$dbh->rollback if $oldAutoCommit;
return $listref_or_error;
foreach my $taxname ( keys %taxname ) {
my $tax = 0;
my %seen = ();
+ my @cust_bill_pkg_tax_location = ();
warn "adding $taxname\n" if $DEBUG > 1;
foreach my $taxitem ( @{ $taxname{$taxname} } ) {
- $tax += $tax{$taxitem} unless $seen{$taxitem};
- $seen{$taxitem} = 1;
+ next if $seen{$taxitem}++;
warn "adding $tax{$taxitem}\n" if $DEBUG > 1;
+ $tax += $tax{$taxitem};
+ push @cust_bill_pkg_tax_location,
+ map { new FS::cust_bill_pkg_tax_location $_ }
+ @{ $tax_location{ $taxitem } };
}
next unless $tax;
'sdate' => '',
'edate' => '',
'itemdesc' => $taxname,
+ 'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
};
}
#create the new invoice
my $cust_bill = new FS::cust_bill ( {
'custnum' => $self->custnum,
- '_date' => ( $options{'invoice_time'} || $time ),
+ '_date' => ( $invoice_time ),
'charged' => $charged,
} );
my $error = $cust_bill->insert;
if ( $@ );
if ( $increment_next_bill ) {
-
- #change this bit to use Date::Manip? CAREFUL with timezones (see
- # mailing list archive)
- my ($sec,$min,$hour,$mday,$mon,$year) =
- (localtime($sdate) )[0,1,2,3,4,5];
- #pro-rating magic - if $recur_prog fiddles $sdate, want to use that
+ my $next_bill = $part_pkg->add_freq($sdate);
+ return "unparsable frequency: ". $part_pkg->freq
+ if $next_bill == -1;
+
+ #pro-rating magic - if $recur_prog fiddled $sdate, want to use that
# only for figuring next bill date, nothing else, so, reset $sdate again
# here
$sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
#no need, its in $hash{last_bill}# my $last_bill = $cust_pkg->last_bill;
$cust_pkg->last_bill($sdate);
- if ( $part_pkg->freq =~ /^\d+$/ ) {
- $mon += $part_pkg->freq;
- until ( $mon < 12 ) { $mon -= 12; $year++; }
- } elsif ( $part_pkg->freq =~ /^(\d+)w$/ ) {
- my $weeks = $1;
- $mday += $weeks * 7;
- } elsif ( $part_pkg->freq =~ /^(\d+)d$/ ) {
- my $days = $1;
- $mday += $days;
- } elsif ( $part_pkg->freq =~ /^(\d+)h$/ ) {
- my $hours = $1;
- $hour += $hours;
- } else {
- return "unparsable frequency: ". $part_pkg->freq;
- }
- $cust_pkg->setfield('bill',
- timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year));
+ $cust_pkg->setfield('bill', $next_bill );
}
my %cust_bill_pkg = ();
my %taxes = ();
- my $prefix =
- ( $conf->exists('tax-ship_address') && length($self->ship_last) )
- ? 'ship_'
- : '';
-
my @classes;
#push @classes, $cust_bill_pkg->usage_classes if $cust_bill_pkg->type eq 'U';
push @classes, $cust_bill_pkg->usage_classes if $cust_bill_pkg->usage;
push @classes, 'setup' if $cust_bill_pkg->setup;
push @classes, 'recur' if $cust_bill_pkg->recur;
- if ( $conf->exists('enable_taxproducts')
- && (scalar($part_pkg->part_pkg_taxoverride) || $part_pkg->has_taxproduct)
- && ( $self->tax !~ /Y/i && $self->payby ne 'COMP' )
- )
- {
+ if ( $self->tax !~ /Y/i && $self->payby ne 'COMP' ) {
- foreach my $class (@classes) {
- my $err_or_ref = $self->_gather_taxes( $part_pkg, $class, $prefix );
- return $err_or_ref unless ref($err_or_ref);
- $taxes{$class} = $err_or_ref;
- }
+ if ( $conf->exists('enable_taxproducts')
+ && ( scalar($part_pkg->part_pkg_taxoverride)
+ || $part_pkg->has_taxproduct
+ )
+ )
+ {
- unless (exists $taxes{''}) {
- my $err_or_ref = $self->_gather_taxes( $part_pkg, '', $prefix );
- return $err_or_ref unless ref($err_or_ref);
- $taxes{''} = $err_or_ref;
- }
+ if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) {
+ return "fatal: Can't (yet) use tax-pkg_address with taxproducts";
+ }
- } elsif ( $self->tax !~ /Y/i && $self->payby ne 'COMP' ) {
+ foreach my $class (@classes) {
+ my $err_or_ref = $self->_gather_taxes( $part_pkg, $class );
+ return $err_or_ref unless ref($err_or_ref);
+ $taxes{$class} = $err_or_ref;
+ }
- my %taxhash = map { $_ => $self->get("$prefix$_") }
- qw( state county country );
+ unless (exists $taxes{''}) {
+ my $err_or_ref = $self->_gather_taxes( $part_pkg, '' );
+ return $err_or_ref unless ref($err_or_ref);
+ $taxes{''} = $err_or_ref;
+ }
- $taxhash{'taxclass'} = $part_pkg->taxclass;
+ } else {
- my @taxes = qsearch( 'cust_main_county', \%taxhash );
+ my @loc_keys = qw( state county country );
+ my %taxhash;
+ if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) {
+ my $cust_location = $cust_pkg->cust_location;
+ %taxhash = map { $_ => $cust_location->$_() } @loc_keys;
+ } else {
+ my $prefix =
+ ( $conf->exists('tax-ship_address') && length($self->ship_last) )
+ ? 'ship_'
+ : '';
+ %taxhash = map { $_ => $self->get("$prefix$_") } @loc_keys;
+ }
- unless ( @taxes ) {
- $taxhash{'taxclass'} = '';
- @taxes = qsearch( 'cust_main_county', \%taxhash );
- }
+ $taxhash{'taxclass'} = $part_pkg->taxclass;
- #one more try at a whole-country tax rate
- unless ( @taxes ) {
- $taxhash{$_} = '' foreach qw( state county );
- @taxes = qsearch( 'cust_main_county', \%taxhash );
- }
+ my @taxes = qsearch( 'cust_main_county', \%taxhash );
- $taxes{''} = [ @taxes ];
- $taxes{'setup'} = [ @taxes ];
- $taxes{'recur'} = [ @taxes ];
- $taxes{$_} = [ @taxes ] foreach (@classes);
-
- # maybe eliminate this entirely, along with all the 0% records
- unless ( @taxes ) {
- return
- "fatal: can't find tax rate for state/county/country/taxclass ".
- join('/', ( map $self->get("$prefix$_"),
- qw(state county country)
- ),
- $part_pkg->taxclass ). "\n";
- }
+ my %taxhash_elim = %taxhash;
+
+ my @elim = qw( taxclass county state );
+ while ( !scalar(@taxes) && scalar(@elim) ) {
+ $taxhash_elim{ shift(@elim) } = '';
+ @taxes = qsearch( 'cust_main_county', \%taxhash_elim );
+ }
- } #if $conf->exists('enable_taxproducts') ...
+ if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) {
+ foreach (@taxes) {
+ $_->set('pkgnum', $cust_pkg->pkgnum );
+ $_->set('locationnum', $cust_pkg->locationnum );
+ }
+ }
+
+ $taxes{''} = [ @taxes ];
+ $taxes{'setup'} = [ @taxes ];
+ $taxes{'recur'} = [ @taxes ];
+ $taxes{$_} = [ @taxes ] foreach (@classes);
+
+ # maybe eliminate this entirely, along with all the 0% records
+ unless ( @taxes ) {
+ return
+ "fatal: can't find tax rate for state/county/country/taxclass ".
+ join('/', map $taxhash{$_}, qw(state county country taxclass) );
+ }
+
+ } #if $conf->exists('enable_taxproducts') ...
+
+ }
my @display = ();
if ( $conf->exists('separate_usage') ) {
my $tax_cust_bill_pkg = $tax_cust_bill_pkg{$key};
foreach my $tax ( @taxes ) {
- my $taxname = ref( $tax ). ' '. $tax->taxnum;
+
+ my $taxname = ref( $tax ). ' taxnum'. $tax->taxnum;
+# $taxname .= ' pkgnum'. $cust_pkg->pkgnum.
+# ' locationnum'. $cust_pkg->locationnum
+# if $conf->exists('tax-pkg_address') && $cust_pkg->locationnum;
+
if ( exists( $taxlisthash->{ $taxname } ) ) {
push @{ $taxlisthash->{ $taxname } }, $tax_cust_bill_pkg;
}else{
my $self = shift;
my $part_pkg = shift;
my $class = shift;
- my $prefix = shift;
my @taxes = ();
my $geocode = $self->geocode('cch');
})
if scalar(@taxclassnums);
- # maybe eliminate this entirely, along with all the 0% records
- unless ( @taxes ) {
- return
- "fatal: can't find tax rate for zip/taxproduct/pkgpart ".
- join('/', ( map $self->get("$prefix$_"),
- qw(zip)
- ),
- $part_pkg->taxproduct_description,
- $part_pkg->pkgpart ). "\n";
- }
-
warn "Found taxes ".
join(',', map{ ref($_). " ". $_->get($_->primary_key) } @taxes). "\n"
if $DEBUG;
Explicitly pass the objects to be tested (typically used with eventtable).
+=item testonly
+
+Set to true to return the objects, but not actually insert them into the
+database.
+
=back
=cut
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- $self->select_for_update; #mutex
+ $self->select_for_update #mutex
+ unless $opt{testonly};
###
# 1: find possible events (initial search)
my $templ_hash = { error => $transaction->error_message };
my $error = send_email(
- 'from' => $conf->config('invoice_from'),
+ 'from' => $conf->config('invoice_from', $self->agentnum ),
'to' => [ grep { $_ ne 'POST' } $self->invoicing_list ],
'subject' => 'Your payment could not be processed',
'body' => [ $template->fill_in(HASH => $templ_hash) ],
}
}
+=item name_short
+
+Returns a name string for this customer, either "Company" or "First Last".
+
+=cut
+
+sub name_short {
+ my $self = shift;
+ $self->company !~ /^\s*$/ ? $self->company : $self->contact_firstlast;
+}
+
+=item ship_name_short
+
+Returns a name string for this (service/shipping) contact, either "Company"
+or "First Last".
+
+=cut
+
+sub ship_name_short {
+ my $self = shift;
+ if ( $self->get('ship_last') ) {
+ $self->ship_company !~ /^\s*$/
+ ? $self->ship_company
+ : $self->ship_contact_firstlast;
+ } else {
+ $self->name_company_or_firstlast;
+ }
+}
+
=item contact
Returns this customer's full (billing) contact name only, "Last, First"
: $self->contact;
}
+=item contact_firstlast
+
+Returns this customers full (billing) contact name only, "First Last".
+
+=cut
+
+sub contact_firstlast {
+ my $self = shift;
+ $self->first. ' '. $self->get('last');
+}
+
+=item ship_contact_firstlast
+
+Returns this customer's full (shipping) contact name only, "First Last".
+
+=cut
+
+sub ship_contact_firstlast {
+ my $self = shift;
+ $self->get('ship_last')
+ ? $self->first. ' '. $self->get('ship_last')
+ : $self->contact_firstlast;
+}
+
=item country_full
Returns this customer's full country name
my $num = $conf->config('cust_main-max_tickets') || 10;
my @tickets = ();
- unless ( $conf->config('ticket_system-custom_priority_field') ) {
+ if ( $conf->config('ticket_system') ) {
+ unless ( $conf->config('ticket_system-custom_priority_field') ) {
- @tickets = @{ FS::TicketSystem->customer_tickets($self->custnum, $num) };
+ @tickets = @{ FS::TicketSystem->customer_tickets($self->custnum, $num) };
- } else {
+ } else {
- foreach my $priority (
- $conf->config('ticket_system-custom_priority_field-values'), ''
- ) {
- last if scalar(@tickets) >= $num;
- push @tickets,
- @{ FS::TicketSystem->customer_tickets( $self->custnum,
- $num - scalar(@tickets),
- $priority,
- )
- };
+ foreach my $priority (
+ $conf->config('ticket_system-custom_priority_field-values'), ''
+ ) {
+ last if scalar(@tickets) >= $num;
+ push @tickets,
+ @{ FS::TicketSystem->customer_tickets( $self->custnum,
+ $num - scalar(@tickets),
+ $priority,
+ )
+ };
+ }
}
}
(@tickets);
=cut
sub notify {
- my ($customer, $template, %options) = @_;
+ my ($self, $template, %options) = @_;
return unless $conf->exists($template);
- my $from = $conf->config('invoice_from') if $conf->exists('invoice_from');
+ my $from = $conf->config('invoice_from', $self->agentnum)
+ if $conf->exists('invoice_from', $self->agentnum);
$from = $options{from} if exists($options{from});
- my $to = join(',', $customer->invoicing_list_emailonly);
+ my $to = join(',', $self->invoicing_list_emailonly);
$to = $options{to} if exists($options{to});
- my $subject = "Notice from " . $conf->config('company_name')
- if $conf->exists('company_name');
+ my $subject = "Notice from " . $conf->config('company_name', $self->agentnum)
+ if $conf->exists('company_name', $self->agentnum);
$subject = $options{subject} if exists($options{subject});
my $notify_template = new Text::Template (TYPE => 'ARRAY',
$notify_template->compile()
or die "can't compile template: Text::Template::ERROR";
- $FS::notify_template::_template::company_name = $conf->config('company_name');
+ $FS::notify_template::_template::company_name =
+ $conf->config('company_name', $self->agentnum);
$FS::notify_template::_template::company_address =
- join("\n", $conf->config('company_address') ). "\n";
-
- my $paydate = $customer->paydate || '2037-12-31';
- $FS::notify_template::_template::first = $customer->first;
- $FS::notify_template::_template::last = $customer->last;
- $FS::notify_template::_template::company = $customer->company;
- $FS::notify_template::_template::payinfo = $customer->mask_payinfo;
- my $payby = $customer->payby;
+ join("\n", $conf->config('company_address', $self->agentnum) ). "\n";
+
+ my $paydate = $self->paydate || '2037-12-31';
+ $FS::notify_template::_template::first = $self->first;
+ $FS::notify_template::_template::last = $self->last;
+ $FS::notify_template::_template::company = $self->company;
+ $FS::notify_template::_template::payinfo = $self->mask_payinfo;
+ my $payby = $self->payby;
my ($payyear,$paymonth,$payday) = split (/-/,$paydate);
my $expire_time = timelocal(0,0,0,$payday,--$paymonth,$payyear);
);
if ( length($retadd) ) {
$letter_data{returnaddress} = $retadd;
- } elsif ( grep /\S/, $conf->config('company_address') ) {
+ } elsif ( grep /\S/, $conf->config('company_address', $self->agentnum) ) {
$letter_data{returnaddress} =
join( '\\*'."\n", map s/( {2,})/'~' x length($1)/eg,
- $conf->config('company_address')
+ $conf->config('company_address', $self->agentnum)
);
} else {
$letter_data{returnaddress} = '~';
$letter_data{conf_dir} = "$FS::UID::conf_dir/conf.$FS::UID::datasrc";
- $letter_data{company_name} = $conf->config('company_name');
+ $letter_data{company_name} = $conf->config('company_name', $self->agentnum);
- my $dir = $FS::UID::conf_dir."cache.". $FS::UID::datasrc;
+ my $dir = $FS::UID::conf_dir."/cache.". $FS::UID::datasrc;
my $fh = new File::Temp( TEMPLATE => 'letter.'. $self->custnum. '.XXXXXXXX',
DIR => $dir,
SUFFIX => '.tex',
do_print [ $self->print_ps($template) ];
}
+#these three subs should just go away once agent stuff is all config overrides
+
sub agent_template {
my $self = shift;
$self->_agent_plandata('agent_templatename');
AND peo_agentnum.optionname = 'agentnum'
AND peo_agentnum.optionvalue }. $regexp. q{ '(^|,)}. $agentnum. q{(,|$)'
)
- LEFT JOIN part_event_option AS peo_cust_bill_age
- ON ( part_event.eventpart = peo_cust_bill_age.eventpart
- AND peo_cust_bill_age.optionname = 'cust_bill_age'
+ LEFT JOIN part_event_condition
+ ON ( part_event.eventpart = part_event_condition.eventpart
+ AND part_event_condition.conditionname = 'cust_bill_age'
+ )
+ LEFT JOIN part_event_condition_option
+ ON ( part_event_condition.eventconditionnum = part_event_condition_option.eventconditionnum
+ AND part_event_condition_option.optionname = 'age'
)
},
#'hashref' => { 'optionname' => $option },
" AND peo_agentnum.optionname = 'agentnum' ".
" AND ( agentnum IS NULL OR agentnum = $agentnum ) ".
" ORDER BY
- CASE WHEN peo_cust_bill_age.optionname != 'cust_bill_age'
+ CASE WHEN part_event_condition_option.optionname IS NULL
THEN -1
- ELSE ". FS::part_event::Condition->age2seconds_sql('peo_cust_bill_age.optionvalue').
+ ELSE ". FS::part_event::Condition->age2seconds_sql('part_event_condition_option.optionvalue').
" END
, part_event.weight".
" LIMIT 1"