package FS::agent;
+use base qw( FS::Commission_Mixin FS::m2m_Common FS::m2name_Common FS::Record );
use strict;
use vars qw( @ISA );
+use Business::CreditCard 0.35;
use FS::Record qw( dbh qsearch qsearchs );
use FS::cust_main;
-use FS::agent_type;
-
-@ISA = qw( FS::Record );
+use FS::cust_pkg;
+use FS::reg_code;
+use FS::agent_payment_gateway;
+use FS::payment_gateway;
+use FS::TicketSystem;
+use FS::Conf;
=head1 NAME
=over 4
-=item agentnum - primary key (assigned automatically for new agents)
+=item agentnum
+
+primary key (assigned automatically for new agents)
+
+=item agent
+
+Text name of this agent
+
+=item typenum
+
+Agent type (see L<FS::agent_type>)
+
+=item ticketing_queueid
+
+Ticketing Queue
+
+=item invoice_template
+
+Invoice template name
+
+=item agent_custnum
+
+Optional agent customer (see L<FS::cust_main>)
-=item agent - Text name of this agent
+=item disabled
-=item typenum - Agent type. See L<FS::agent_type>
+Disabled flag, empty or 'Y'
-=item prog - For future use.
+=item prog
-=item freq - For future use.
+Deprecated (never used)
-=item disabled - Disabled flag, empty or 'Y'
+=item freq
-=item username - Username for the Agent interface
+Deprecated (never used)
-=item _password - Password for the Agent interface
+=item username
+
+(Deprecated) Username for the Agent interface
+
+=item _password
+
+(Deprecated) Password for the Agent interface
=back
|| $self->ut_number('typenum')
|| $self->ut_numbern('freq')
|| $self->ut_textn('prog')
+ || $self->ut_textn('invoice_template')
+ || $self->ut_foreign_keyn('agent_custnum', 'cust_main', 'custnum' )
+ || $self->ut_numbern('ticketing_queueid')
;
return $error if $error;
if ( length($self->username) ) {
my $conflict = qsearchs('agent', { 'username' => $self->username } );
return 'duplicate agent username (with '. $conflict->agent. ')'
- if $conflict;
+ if $conflict && $conflict->agentnum != $self->agentnum;
$error = $self->ut_text('password'); # ut_text... arbitrary choice
} else {
$self->_password('');
Returns the FS::agent_type object (see L<FS::agent_type>) for this agent.
+=item agent_cust_main
+
+Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
+agent.
+
=cut
-sub agent_type {
+sub agent_cust_main {
my $self = shift;
- qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
+ qsearchs( 'cust_main', { 'custnum' => $self->agent_custnum } );
+}
+
+=item agent_currency
+
+Returns the FS::agent_currency objects (see L<FS::agent_currency>), if any, for
+this agent.
+
+=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
$self->agent_type->pkgpart_hashref;
}
+=item ticketing_queue
+
+Returns the queue name corresponding with the id from the I<ticketing_queueid>
+field, or the empty string.
+
+=cut
+
+sub ticketing_queue {
+ my $self = shift;
+ FS::TicketSystem->queue($self->ticketing_queueid);
+}
+
+=item payment_gateway [ OPTION => VALUE, ... ]
+
+Returns a payment gateway object (see L<FS::payment_gateway>) for this agent.
+
+Currently available options are I<nofatal>, I<method>, I<thirdparty> and I<conf>.
+
+If I<nofatal> is set, and no gateway is available, then the empty string
+will be returned instead of throwing a fatal exception.
+
+The I<method> option can be used to influence the choice
+as well. Presently only CHEK/ECHECK and PAYPAL methods are meaningful.
+
+If I<method> is CHEK/ECHECK and the default gateway is being returned,
+the business-onlinepayment-ach gateway will be returned if available.
+
+If I<thirdparty> is set and the I<method> is PAYPAL, the defined paypal
+gateway will be returned.
+
+Exisisting I<$conf> may be passed for efficiency.
+
+=cut
+
+# opts invnum/payinfo for cardtype/taxclass overrides no longer supported
+# any future overrides added here need to be reconciled with the tokenization process
+
+sub payment_gateway {
+ my ( $self, %options ) = @_;
+
+ $options{'conf'} ||= new FS::Conf;
+ my $conf = $options{'conf'};
+
+ if ( $options{thirdparty} ) {
+
+ # allows PayPal to coexist with credit card gateways
+ my $is_paypal = { op => '!=', value => 'PayPal' };
+ if ( uc($options{method}) eq 'PAYPAL' ) {
+ $is_paypal = 'PayPal';
+ }
+
+ my $gateway = qsearchs({
+ table => 'payment_gateway',
+ addl_from => ' JOIN agent_payment_gateway USING (gatewaynum) ',
+ hashref => {
+ gateway_namespace => 'Business::OnlineThirdPartyPayment',
+ gateway_module => $is_paypal,
+ disabled => '',
+ },
+ extra_sql => ' AND agentnum = '.$self->agentnum,
+ });
+
+ if ( $gateway ) {
+ return $gateway;
+ } elsif ( $options{'nofatal'} ) {
+ return '';
+ } else {
+ die "no third-party gateway configured\n";
+ }
+ }
+
+ my $cardtype_search = "AND ( cardtype IS NULL OR cardtype <> 'ACH')";
+ $cardtype_search = "AND ( cardtype IS NULL OR cardtype = 'ACH' )" if $options{method} eq 'ECHECK';
+
+ my $override =
+ qsearchs({
+ "table" => 'agent_payment_gateway',
+ "hashref" => { agentnum => $self->agentnum, },
+ "extra_sql" => $cardtype_search,
+ });
+
+ my $payment_gateway = FS::payment_gateway->by_key_or_default(
+ gatewaynum => $override ? $override->gatewaynum : '',
+ %options,
+ );
+
+ $payment_gateway;
+}
+
+=item invoice_modes
+
+Returns all L<FS::invoice_mode> objects that are valid for this agent (i.e.
+those with this agentnum or null agentnum).
+
+=cut
+
+sub invoice_modes {
+ my $self = shift;
+ qsearch( {
+ table => 'invoice_mode',
+ hashref => { agentnum => $self->agentnum },
+ extra_sql => ' OR agentnum IS NULL',
+ order_by => ' ORDER BY modename',
+ } );
+}
+
=item num_prospect_cust_main
Returns the number of prospects (customers with no packages ever ordered) for
sub num_sql {
my( $self, $sql ) = @_;
- my $sth = dbh->prepare(
- "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql"
- ) or die dbh->errstr;
- $sth->execute($self->agentnum) or die $sth->errstr;
+ my $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql";
+ my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
+ $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement";
$sth->fetchrow_arrayref->[0];
}
);
}
+=item num_ordered_cust_main
+
+Returns the number of ordered customers for this agent (customers with packages
+ordered, but not yet billed).
+
+=cut
+
+sub num_ordered_cust_main {
+ shift->num_sql(FS::cust_main->ordered_sql);
+}
+
+=item ordered_cust_main
+
+Returns the ordered customers for this agent (customers with packages ordered,
+but not yet billed), as cust_main objects.
+
+=cut
+
+sub ordered_cust_main {
+ shift->cust_main_sql(FS::cust_main->ordered_sql);
+}
+
+
=item num_active_cust_main
-Returns the number of active customers for this agent.
+Returns the number of active customers for this agent (customers with active
+recurring packages).
=cut
shift->cust_main_sql(FS::cust_main->active_sql);
}
+=item num_inactive_cust_main
+
+Returns the number of inactive customers for this agent (customers with no
+active recurring packages, but otherwise unsuspended/uncancelled).
+
+=cut
+
+sub num_inactive_cust_main {
+ shift->num_sql(FS::cust_main->inactive_sql);
+}
+
+=item inactive_cust_main
+
+Returns the inactive customers for this agent, as cust_main objects.
+
+=cut
+
+sub inactive_cust_main {
+ shift->cust_main_sql(FS::cust_main->inactive_sql);
+}
+
+
=item num_susp_cust_main
Returns the number of suspended customers for this agent.
shift->cust_main_sql(FS::cust_main->cancel_sql);
}
+=item num_active_cust_pkg
+
+Returns the number of active customer packages for this agent.
+
+=cut
+
+sub num_active_cust_pkg {
+ shift->num_pkg_sql(FS::cust_pkg->active_sql);
+}
+
+sub num_pkg_sql {
+ my( $self, $sql ) = @_;
+ my $statement =
+ "SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )".
+ " WHERE agentnum = ? AND $sql";
+ my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
+ $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement";
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item num_inactive_cust_pkg
+
+Returns the number of inactive customer packages (one-time packages otherwise
+unsuspended/uncancelled) for this agent.
+
+=cut
+
+sub num_inactive_cust_pkg {
+ shift->num_pkg_sql(FS::cust_pkg->inactive_sql);
+}
+
+=item num_susp_cust_pkg
+
+Returns the number of suspended customer packages for this agent.
+
+=cut
+
+sub num_susp_cust_pkg {
+ shift->num_pkg_sql(FS::cust_pkg->susp_sql);
+}
+
+=item num_cancel_cust_pkg
+
+Returns the number of cancelled customer packages for this agent.
+
+=cut
+
+sub num_cancel_cust_pkg {
+ shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
+}
+
+=item num_on_hold_cust_pkg
+
+Returns the number of inactive customer packages (one-time packages otherwise
+unsuspended/uncancelled) for this agent.
+
+=cut
+
+sub num_on_hold_cust_pkg {
+ shift->num_pkg_sql(FS::cust_pkg->on_hold_sql);
+}
+
+=item num_not_yet_billed_cust_pkg
+
+Returns the number of inactive customer packages (one-time packages otherwise
+unsuspended/uncancelled) for this agent.
+
+=cut
+
+sub num_not_yet_billed_cust_pkg {
+ shift->num_pkg_sql(FS::cust_pkg->not_yet_billed_sql);
+}
+
+=item generate_reg_codes NUM PKGPART_ARRAYREF
+
+Generates the specified number of registration codes, allowing purchase of the
+specified package definitions. Returns an array reference of the newly
+generated codes, or a scalar error message.
+
+=cut
+
+#false laziness w/prepay_credit::generate
+sub generate_reg_codes {
+ my( $self, $num, $pkgparts ) = @_;
+
+ my @codeset = ( 'A'..'Z' );
+
+ 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;
+
+ my @codes = ();
+ for ( 1 ... $num ) {
+ my $reg_code = new FS::reg_code {
+ 'agentnum' => $self->agentnum,
+ 'code' => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
+ };
+ my $error = $reg_code->insert($pkgparts);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ push @codes, $reg_code->code;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ \@codes;
+
+}
+
+=item num_reg_code
+
+Returns the number of unused registration codes for this agent.
+
+=cut
+
+sub num_reg_code {
+ my $self = shift;
+ my $sth = dbh->prepare(
+ "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
+ ) or die dbh->errstr;
+ $sth->execute($self->agentnum) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item num_prepay_credit
+
+Returns the number of unused prepaid cards for this agent.
+
+=cut
+
+sub num_prepay_credit {
+ my $self = shift;
+ my $sth = dbh->prepare(
+ "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
+ ) or die dbh->errstr;
+ $sth->execute($self->agentnum) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item num_sales
+
+Returns the number of non-disabled sales people for this agent.
+
+=cut
+
+sub num_sales {
+ my $self = shift;
+ my $sth = dbh->prepare(
+ "SELECT COUNT(*) FROM sales WHERE agentnum = ?
+ AND ( disabled = '' OR disabled IS NULL )"
+ ) or die dbh->errstr;
+ $sth->execute($self->agentnum) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+sub commission_where {
+ my $self = shift;
+ 'cust_credit.commission_agentnum = ' . $self->agentnum;
+}
+
+sub sales_where {
+ my $self = shift;
+ 'cust_main.agentnum = ' . $self->agentnum;
+}
+
=back
=head1 BUGS