2 use base qw( FS::Commission_Mixin FS::m2m_Common FS::m2name_Common FS::Record );
6 use Business::CreditCard 0.35;
7 use FS::Record qw( dbh qsearch qsearchs );
11 use FS::agent_payment_gateway;
17 FS::agent - Object methods for agent records
23 $record = new FS::agent \%hash;
24 $record = new FS::agent { 'column' => 'value' };
26 $error = $record->insert;
28 $error = $new_record->replace($old_record);
30 $error = $record->delete;
32 $error = $record->check;
34 $agent_type = $record->agent_type;
36 $hashref = $record->pkgpart_hashref;
37 #may purchase $pkgpart if $hashref->{$pkgpart};
41 An FS::agent object represents an agent. Every customer has an agent. Agents
42 can be used to track things like resellers or salespeople. FS::agent inherits
43 from FS::Record. The following fields are currently supported:
49 primary key (assigned automatically for new agents)
53 Text name of this agent
57 Agent type (see L<FS::agent_type>)
59 =item ticketing_queueid
63 =item invoice_template
69 Optional agent customer (see L<FS::cust_main>)
73 Disabled flag, empty or 'Y'
77 Deprecated (never used)
81 Deprecated (never used)
85 (Deprecated) Username for the Agent interface
89 (Deprecated) Password for the Agent interface
99 Creates a new agent. To add the agent to the database, see L<"insert">.
103 sub table { 'agent'; }
107 Adds this agent to the database. If there is an error, returns the error,
108 otherwise returns false.
112 Deletes this agent from the database. Only agents with no customers can be
113 deleted. If there is an error, returns the error, otherwise returns false.
120 return "Can't delete an agent with customers!"
121 if qsearch( 'cust_main', { 'agentnum' => $self->agentnum } );
123 $self->SUPER::delete;
126 =item replace OLD_RECORD
128 Replaces OLD_RECORD with this one in the database. If there is an error,
129 returns the error, otherwise returns false.
133 Checks all fields to make sure this is a valid agent. If there is an error,
134 returns the error, otherwise returns false. Called by the insert and replace
143 $self->ut_numbern('agentnum')
144 || $self->ut_text('agent')
145 || $self->ut_number('typenum')
146 || $self->ut_numbern('freq')
147 || $self->ut_textn('prog')
148 || $self->ut_textn('invoice_template')
149 || $self->ut_foreign_keyn('agent_custnum', 'cust_main', 'custnum' )
150 || $self->ut_numbern('ticketing_queueid')
152 return $error if $error;
154 if ( $self->dbdef_table->column('disabled') ) {
155 $error = $self->ut_enum('disabled', [ '', 'Y' ] );
156 return $error if $error;
159 if ( $self->dbdef_table->column('username') ) {
160 $error = $self->ut_alphan('username');
161 return $error if $error;
162 if ( length($self->username) ) {
163 my $conflict = qsearchs('agent', { 'username' => $self->username } );
164 return 'duplicate agent username (with '. $conflict->agent. ')'
165 if $conflict && $conflict->agentnum != $self->agentnum;
166 $error = $self->ut_text('password'); # ut_text... arbitrary choice
168 $self->_password('');
172 return "Unknown typenum!"
173 unless $self->agent_type;
180 Returns the FS::agent_type object (see L<FS::agent_type>) for this agent.
182 =item agent_cust_main
184 Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
189 sub agent_cust_main {
191 qsearchs( 'cust_main', { 'custnum' => $self->agent_custnum } );
196 Returns the FS::agent_currency objects (see L<FS::agent_currency>), if any, for
199 =item agent_currency_hashref
201 Returns a hash references of supported additional currencies for this agent.
205 sub agent_currency_hashref {
207 +{ map { $_->currency => 1 }
208 $self->agent_currency
212 =item pkgpart_hashref
214 Returns a hash reference. The keys of the hash are pkgparts. The value is
215 true if this agent may purchase the specified package definition. See
220 sub pkgpart_hashref {
222 $self->agent_type->pkgpart_hashref;
225 =item ticketing_queue
227 Returns the queue name corresponding with the id from the I<ticketing_queueid>
228 field, or the empty string.
232 sub ticketing_queue {
234 FS::TicketSystem->queue($self->ticketing_queueid);
237 =item payment_gateway [ OPTION => VALUE, ... ]
239 Returns a payment gateway object (see L<FS::payment_gateway>) for this agent.
241 Currently available options are I<nofatal>, I<method>, I<thirdparty>,
242 <conf> and I<load_gatewaynum>.
244 If I<nofatal> is set, and no gateway is available, then the empty string
245 will be returned instead of throwing a fatal exception.
247 The I<method> option can be used to influence the choice
248 as well. Presently only CHEK/ECHECK and PAYPAL methods are meaningful.
250 If I<method> is CHEK/ECHECK and the default gateway is being returned,
251 the business-onlinepayment-ach gateway will be returned if available.
253 If I<thirdparty> is set and the I<method> is PAYPAL, the defined paypal
254 gateway will be returned.
256 If I<load_gatewaynum> exists, then either the specified gateway or the
257 default gateway will be returned. Agent overrides are ignored, and this can
258 safely be called as a class method if this option is specified. Not
259 compatible with I<thirdparty>.
261 Exsisting I<$conf> may be passed for efficiency.
265 # opts invnum/payinfo for cardtype/taxclass overrides no longer supported
266 # any future overrides added here need to be reconciled with the tokenization process
268 sub payment_gateway {
269 my ( $self, %options ) = @_;
271 my $conf = $options{'conf'};
272 $conf ||= new FS::Conf;
274 if ( $options{thirdparty} ) {
276 # allows PayPal to coexist with credit card gateways
277 my $is_paypal = { op => '!=', value => 'PayPal' };
278 if ( uc($options{method}) eq 'PAYPAL' ) {
279 $is_paypal = 'PayPal';
282 my $gateway = qsearchs({
283 table => 'payment_gateway',
284 addl_from => ' JOIN agent_payment_gateway USING (gatewaynum) ',
286 gateway_namespace => 'Business::OnlineThirdPartyPayment',
287 gateway_module => $is_paypal,
290 extra_sql => ' AND agentnum = '.$self->agentnum,
295 } elsif ( $options{'nofatal'} ) {
298 die "no third-party gateway configured\n";
302 my ($override, $payment_gateway);
303 if (exists $options{'load_gatewaynum'}) { # no agent overrides if this opt is in use
304 if ($options{'load_gatewaynum'}) {
305 $payment_gateway = qsearchs('payment_gateway', { gatewaynumnum => $options{'load_gatewaynum'} } );
307 die "Could not load payment gateway ".$options{'load_gatewaynum'} unless $payment_gateway;
308 } # else use default, loaded below
310 $override = qsearchs('agent_payment_gateway', { agentnum => $self->agentnum } );
313 if ( $override ) { #use a payment gateway override
315 $payment_gateway = $override->payment_gateway;
317 $payment_gateway->gateway_namespace('Business::OnlinePayment')
318 unless $payment_gateway->gateway_namespace;
320 } elsif (!$payment_gateway) { #use the standard settings from the config
322 # the standard settings from the config could be moved to a null agent
323 # agent_payment_gateway referenced payment_gateway
325 # remember, this block might be run as a class method if false load_gatewaynum exists
327 unless ( $conf->exists('business-onlinepayment') ) {
328 if ( $options{'nofatal'} ) {
331 die "Real-time processing not enabled\n";
336 my $bop_config = 'business-onlinepayment';
337 $bop_config .= '-ach'
338 if ( $options{method}
339 && $options{method} =~ /^(ECHECK|CHEK)$/
340 && $conf->exists($bop_config. '-ach')
342 my ( $processor, $login, $password, $action, @bop_options ) =
343 $conf->config($bop_config);
344 $action ||= 'normal authorization';
345 pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
346 die "No real-time processor is enabled - ".
347 "did you set the business-onlinepayment configuration value?\n"
350 $payment_gateway = new FS::payment_gateway;
352 $payment_gateway->gateway_namespace( $conf->config('business-onlinepayment-namespace') ||
353 'Business::OnlinePayment');
354 $payment_gateway->gateway_module($processor);
355 $payment_gateway->gateway_username($login);
356 $payment_gateway->gateway_password($password);
357 $payment_gateway->gateway_action($action);
358 $payment_gateway->set('options', [ @bop_options ]);
362 unless ( $payment_gateway->gateway_namespace ) {
363 $payment_gateway->gateway_namespace(
364 scalar($conf->config('business-onlinepayment-namespace'))
365 || 'Business::OnlinePayment'
374 Returns all L<FS::invoice_mode> objects that are valid for this agent (i.e.
375 those with this agentnum or null agentnum).
382 table => 'invoice_mode',
383 hashref => { agentnum => $self->agentnum },
384 extra_sql => ' OR agentnum IS NULL',
385 order_by => ' ORDER BY modename',
389 =item num_prospect_cust_main
391 Returns the number of prospects (customers with no packages ever ordered) for
396 sub num_prospect_cust_main {
397 shift->num_sql(FS::cust_main->prospect_sql);
401 my( $self, $sql ) = @_;
402 my $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql";
403 my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
404 $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement";
405 $sth->fetchrow_arrayref->[0];
408 =item prospect_cust_main
410 Returns the prospects (customers with no packages ever ordered) for this agent,
411 as cust_main objects.
415 sub prospect_cust_main {
416 shift->cust_main_sql(FS::cust_main->prospect_sql);
420 my( $self, $sql ) = @_;
421 qsearch( 'cust_main',
422 { 'agentnum' => $self->agentnum },
428 =item num_ordered_cust_main
430 Returns the number of ordered customers for this agent (customers with packages
431 ordered, but not yet billed).
435 sub num_ordered_cust_main {
436 shift->num_sql(FS::cust_main->ordered_sql);
439 =item ordered_cust_main
441 Returns the ordered customers for this agent (customers with packages ordered,
442 but not yet billed), as cust_main objects.
446 sub ordered_cust_main {
447 shift->cust_main_sql(FS::cust_main->ordered_sql);
451 =item num_active_cust_main
453 Returns the number of active customers for this agent (customers with active
458 sub num_active_cust_main {
459 shift->num_sql(FS::cust_main->active_sql);
462 =item active_cust_main
464 Returns the active customers for this agent, as cust_main objects.
468 sub active_cust_main {
469 shift->cust_main_sql(FS::cust_main->active_sql);
472 =item num_inactive_cust_main
474 Returns the number of inactive customers for this agent (customers with no
475 active recurring packages, but otherwise unsuspended/uncancelled).
479 sub num_inactive_cust_main {
480 shift->num_sql(FS::cust_main->inactive_sql);
483 =item inactive_cust_main
485 Returns the inactive customers for this agent, as cust_main objects.
489 sub inactive_cust_main {
490 shift->cust_main_sql(FS::cust_main->inactive_sql);
494 =item num_susp_cust_main
496 Returns the number of suspended customers for this agent.
500 sub num_susp_cust_main {
501 shift->num_sql(FS::cust_main->susp_sql);
506 Returns the suspended customers for this agent, as cust_main objects.
511 shift->cust_main_sql(FS::cust_main->susp_sql);
514 =item num_cancel_cust_main
516 Returns the number of cancelled customer for this agent.
520 sub num_cancel_cust_main {
521 shift->num_sql(FS::cust_main->cancel_sql);
524 =item cancel_cust_main
526 Returns the cancelled customers for this agent, as cust_main objects.
530 sub cancel_cust_main {
531 shift->cust_main_sql(FS::cust_main->cancel_sql);
534 =item num_active_cust_pkg
536 Returns the number of active customer packages for this agent.
540 sub num_active_cust_pkg {
541 shift->num_pkg_sql(FS::cust_pkg->active_sql);
545 my( $self, $sql ) = @_;
547 "SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )".
548 " WHERE agentnum = ? AND $sql";
549 my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
550 $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement";
551 $sth->fetchrow_arrayref->[0];
554 =item num_inactive_cust_pkg
556 Returns the number of inactive customer packages (one-time packages otherwise
557 unsuspended/uncancelled) for this agent.
561 sub num_inactive_cust_pkg {
562 shift->num_pkg_sql(FS::cust_pkg->inactive_sql);
565 =item num_susp_cust_pkg
567 Returns the number of suspended customer packages for this agent.
571 sub num_susp_cust_pkg {
572 shift->num_pkg_sql(FS::cust_pkg->susp_sql);
575 =item num_cancel_cust_pkg
577 Returns the number of cancelled customer packages for this agent.
581 sub num_cancel_cust_pkg {
582 shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
585 =item num_on_hold_cust_pkg
587 Returns the number of inactive customer packages (one-time packages otherwise
588 unsuspended/uncancelled) for this agent.
592 sub num_on_hold_cust_pkg {
593 shift->num_pkg_sql(FS::cust_pkg->on_hold_sql);
596 =item num_not_yet_billed_cust_pkg
598 Returns the number of inactive customer packages (one-time packages otherwise
599 unsuspended/uncancelled) for this agent.
603 sub num_not_yet_billed_cust_pkg {
604 shift->num_pkg_sql(FS::cust_pkg->not_yet_billed_sql);
607 =item generate_reg_codes NUM PKGPART_ARRAYREF
609 Generates the specified number of registration codes, allowing purchase of the
610 specified package definitions. Returns an array reference of the newly
611 generated codes, or a scalar error message.
615 #false laziness w/prepay_credit::generate
616 sub generate_reg_codes {
617 my( $self, $num, $pkgparts ) = @_;
619 my @codeset = ( 'A'..'Z' );
621 local $SIG{HUP} = 'IGNORE';
622 local $SIG{INT} = 'IGNORE';
623 local $SIG{QUIT} = 'IGNORE';
624 local $SIG{TERM} = 'IGNORE';
625 local $SIG{TSTP} = 'IGNORE';
626 local $SIG{PIPE} = 'IGNORE';
628 my $oldAutoCommit = $FS::UID::AutoCommit;
629 local $FS::UID::AutoCommit = 0;
634 my $reg_code = new FS::reg_code {
635 'agentnum' => $self->agentnum,
636 'code' => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
638 my $error = $reg_code->insert($pkgparts);
640 $dbh->rollback if $oldAutoCommit;
643 push @codes, $reg_code->code;
646 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
654 Returns the number of unused registration codes for this agent.
660 my $sth = dbh->prepare(
661 "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
662 ) or die dbh->errstr;
663 $sth->execute($self->agentnum) or die $sth->errstr;
664 $sth->fetchrow_arrayref->[0];
667 =item num_prepay_credit
669 Returns the number of unused prepaid cards for this agent.
673 sub num_prepay_credit {
675 my $sth = dbh->prepare(
676 "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
677 ) or die dbh->errstr;
678 $sth->execute($self->agentnum) or die $sth->errstr;
679 $sth->fetchrow_arrayref->[0];
684 Returns the number of non-disabled sales people for this agent.
690 my $sth = dbh->prepare(
691 "SELECT COUNT(*) FROM sales WHERE agentnum = ?
692 AND ( disabled = '' OR disabled IS NULL )"
693 ) or die dbh->errstr;
694 $sth->execute($self->agentnum) or die $sth->errstr;
695 $sth->fetchrow_arrayref->[0];
698 sub commission_where {
700 'cust_credit.commission_agentnum = ' . $self->agentnum;
705 'cust_main.agentnum = ' . $self->agentnum;
714 L<FS::Record>, L<FS::agent_type>, L<FS::cust_main>, L<FS::part_pkg>,
715 schema.html from the base documentation.